![]() |
|
Spaces home So long, and thanks for ...ProfileFriendsBlogMore ![]() | ![]() |
So long, and thanks for all the chips |
|||||
|
September 04 Background Task in WPFWriting multi-threaded code is hard. Very hard. Writing multi-threaded UI code is even harder. Much harder. See, user interface components are generally not thread safe. You can't modify properties or call methods on user interface components safely, unless you're in the same thread on which the component was created. This has been true on every UI framework I've ever dealt with, and it's true in WPF. This makes it very difficult to create multi-threaded UI code, because you have to constantly be marshalling operations across thread boundaries, which is both more complicated to code, and leads to the potential for race conditions and deadlocks. WPF has helped to make the marshalling of operations across thread boundaries a little easier with the DispatcherObject class. DispatcherObject is generally used as a base class for other types, and all of the WPF UI components derive from DispatcherObject. When a DispatcherObject is constructed, it associates itself with the currently running thread. A couple of methods, CheckAccess and VerifyAccess, allow you to determine if the current thread is the same thread that the DispatcherObject was created on. CheckAccess simply returns a boolean, while VerifyAccess will throw an exception if the current thread isn't the associated thread on which the DispatcherObject was created. By convention, any property or method that must be called only when running on the associated thread will call VerifyAccess internally, thus ensuring they can't be called except from the thread on which the DispatcherObject was created. So, we understand how DispatcherObject ensures that operations occur only on the associated thread. How does it help us to marshal operations onto this thread? DispatcherObject has a Dispatcher property that includes various overloads of Invoke and BeginInvoke that marshal operations onto the DispatcherObject's associated thread. All of this greatly simplifies the coding required to create objects with thread affinity and to marshal operations across thread boundaries. You should be aware that this isn't a silver bullet solution. The marshalling can still lead to deadlock. The general idea behind marshalling operations across thread boundaries is to bundle up the operation and put it in a queue where the other thread can extract it and run the operation. This requires the other thread to actually participate by checking the queue and running the operation. If that thread is busy doing something else, it won't be checking the queue and the operation will be sitting there. So, if one thread is marshalling to another, and that thread is try to marshal to the first, deadlock can ensue. So, you must be careful here, even though the DispatcherObject has made it easier to coordinate between threads. WinForms had a somewhat similar concept to DispatcherObject in the SynchronizationContext class, though the DispatcherObject is a cleaner and easier to use design. Score one for WPF. So, with the DispatcherObject or SynchronizationContext classes, it's possible to create background operations that communicate with our UI by marshalling operations onto the "UI thread" (the thread on which the UI components are created). However, there's still a lot of dirty little details you have to take into account when creating such a background process. So, WinForms provided us with a BackgroundWorker class. This class would greatly simplify the code required to create a background task, allow it to be cancelled and report progress on the UI thread. WPF doesn't have a similar concept, so score one for WinForms. Other people have pointed out that you can use the BackgroundWorker in a WPF application, and there's plenty of examples of how to do this. It's not rocket science, really, so I'm not going to cover that. What I am going to do, and the whole purpose of the post, is tell you that BackgroundWorker is a sub-optimal solution in WPF, and provide you with the basis of a better solution. Why is BackgroundWorker sub-optimal? Because the design requires you to use an event handler in order to update your UI on the progress of the operation. This is WPF. We don't want to have to write an event handler that will update our UI. We want to use data binding and no code at all! This is fairly easy to achieve. I'm not going to provide code for this here, because I'm still working on the concept. But it should be easy enough for you to take the idea I'm about to describe and run with it. What I'm working on is what I'm calling a BackgroundTask. This is a class that works very similarly to the way the BackgroundWorker does. However, there is no ProgressChanged event on the BackgroundTask. Instead, there's a PercentProgress property which the UI can data bind to. BackgroundTask is a DispatcherObject, so it has thread affinity, and by convention should be created on the "UI thread". The background process will then use the magic of DispatcherObject to marshal changes to the PercentProgress property onto the UI thread, where INotifyPropertyChanged can notify the data binding that it needs to be updated. This is a very simple concept, but the result is a much easier to use component in WPF. More importantly, this is just the tip of the iceberg for what's possible with the idea. Imagine we give these BackgroundTask objects a Title property that can also be used in data binding to display on the UI. Then imagine a BackgroundTaskCollection that holds running BackgroundTask instances (which would be automatically removed from the collection when the task completes). Now our UI can display information about multiple running background tasks through simple WPF data binding magic. Now imagine we have other properties on the BackgroundTask that can also be used in data binding to display even richer information about our background process. Doesn't this concept start to sound very exciting? Now, here's another huge bonus to this design. Let's imagine we have some domain model (data model, what ever name you wish to use) objects which our UI displays through data binding. Imagine that these models are not thread safe (they probably shouldn't be). Then imagine we need to add some long running process that's going to modify these domain objects. We don't want to run this process on the UI thread, because it would cause our UI to become unresponsive for an extended period of time. We don't want to, or can't, modify the domain object, however, in order to make it thread safe. So, what do we do? Well, you don't have to do much if you're using a BackgroundTask concept like I just described. The operation you're running in the background can use the BackgroundTask to marshal operations that modify the domain object onto the "UI thread". Your UI will update itself through the magic of data binding. Problem solved with a minimal amount of code, and no impact to your current domain object design. August 20 The first rule of WPFAvoid control inheritance. I knew this rule. I've read it several times from many different sources. I've even given this advice to others. However, I'm also guilty of forgetting this rule. Recently, I had the need for a MaskedTextBox in WPF. Marlon Grech, a fellow WPF Disciple, has a great article about how he created such a control for his AvalonControlsLibrary. Having read that post, my first inclination was to create my own MaskedTextBox (not a matter of NIH syndrome, but the lesser evil of simply not wanting to deal with the legal paperwork necessary to use Marlon's code at work). I spent a few days perfecting this control (there's a lot of corner cases you have to deal with, and I wanted full MaskedTextBox functionality, which makes it even more difficult). Once I was done, a coworker pointed out to me that we didn't pick up the Aero styling that our application uses. It's a simple fix at an application level, but not so easy to fix at a library level. Not a huge deal, but this got me to thinking about how to address the problem. Then it dawned on me... I shouldn't have created a MaskedTextBox at all! You see, if I'd followed the "first rule of WPF", I wouldn't have had the styling issue. So, what's the better solution here, then? Well, you simply use attached properties and behaviors instead! Here's a naive example of this concept. Please beware that this is NOT a complete solution, and won't begin to work for you as is. It only is here for illustrative purposes. I can't publish a complete solution since I developed this during work hours. However, with this illustration, it shouldn't be too difficult to develop a complete solution. Anyway, on to the code. public class MaskedText : IDisposable { private readonly TextBox _target; private MaskedTextProvider _provider; private MaskedText(TextBox target) { _target = target; _target.PreviewTextInput += OnPreviewTextInput; CreateProvider(); } public void Dispose() { _target.PreviewTextInput -= OnPreviewTextInput; } private void OnPreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e) { e.Handled = true; ReplaceText(e.Text); } private void CreateProvider() { string text = _target.Text; if (_provider != null) { text = _provider.ToString(false, false); } _provider = new MaskedTextProvider( GetMask(_target), CultureInfo.CurrentCulture, true, // allow prompt as input GetPromptChar(_target), '\0', false); // Ascii only int testPosition; MaskedTextResultHint hint; _provider.Replace(text, 0, _provider.Length, out testPosition, out hint); SetText(); } private void SetText() { int start = _target.SelectionStart; _target.Text = GetFormattedDisplayString(); _target.SelectionStart = start; _target.SelectionLength = 0; } private string GetFormattedDisplayString() { bool flag; if (_target.IsReadOnly) { flag = false; } else if (DesignerProperties.GetIsInDesignMode(_target)) { flag = true; } else { flag = !GetHidePromptOnLeave(_target) || _target.IsFocused; } return _provider.ToString(false, flag, true, 0, _provider.Length); } private void ReplaceText(string text) { int start = _target.SelectionStart; int end = start + _target.SelectionLength - 1; int testPosition; MaskedTextResultHint hint; if (end >= start) { _provider.Replace(text, start, end, out testPosition, out hint); } else { _provider.InsertAt(text, start, out testPosition, out hint); } if (hint == MaskedTextResultHint.Success || hint == MaskedTextResultHint.CharacterEscaped || hint == MaskedTextResultHint.NoEffect || hint == MaskedTextResultHint.SideEffect) { SetText(); _target.SelectionStart = testPosition + text.Length; _target.SelectionLength = 0; } else { //RaiseMaskInputRejectedEvent(hint, start); } } #region Instance private static readonly DependencyProperty InstanceProperty = DependencyProperty.RegisterAttached("Instance", typeof(MaskedText), typeof(MaskedText), new FrameworkPropertyMetadata((MaskedText)null)); private static MaskedText GetInstance(DependencyObject d) { return (MaskedText)d.GetValue(InstanceProperty); } private static void SetInstance(DependencyObject d, MaskedText value) { d.SetValue(InstanceProperty, value); } #endregion #region Mask public static readonly DependencyProperty MaskProperty = DependencyProperty.RegisterAttached( "Mask", typeof(string), typeof(MaskedText), new FrameworkPropertyMetadata((string)null, new PropertyChangedCallback(OnMaskChanged), new CoerceValueCallback(CoerceMaskValue)), new ValidateValueCallback(IsMaskValid)); public static string GetMask(DependencyObject d) { return (string)d.GetValue(MaskProperty); } public static void SetMask(DependencyObject d, string value) { d.SetValue(MaskProperty, value); } private static void OnMaskChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (e.NewValue == null) { Unattach(d); } else { Attach(d); } } private static object CoerceMaskValue(DependencyObject d, object value) { string mask = (string)value; if (string.IsNullOrEmpty(mask)) { return null; } return value; } private static bool IsMaskValid(object value) { string mask = (string)value; if (string.IsNullOrEmpty(mask)) { return true; } foreach (char ch in mask) { if (!MaskedTextProvider.IsValidMaskChar(ch)) { return false; } } return true; } #endregion #region PromptChar public static readonly DependencyProperty PromptCharProperty = DependencyProperty.RegisterAttached( "PromptChar", typeof(char), typeof(MaskedText), new FrameworkPropertyMetadata('_', FrameworkPropertyMetadataOptions.None, new PropertyChangedCallback(OnPromptCharChanged)), new ValidateValueCallback(IsPromptCharValid)); public static char GetPromptChar(DependencyObject d) { return (char)d.GetValue(PromptCharProperty); } public static void SetPromptChar(DependencyObject d, char value) { d.SetValue(PromptCharProperty, value); } private static void OnPromptCharChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { MaskedText maskedText = GetInstance(d); if (maskedText != null) { maskedText._provider.PromptChar = (char)e.NewValue; maskedText.SetText(); } } private static bool IsPromptCharValid(object value) { char ch = (char)value; return MaskedTextProvider.IsValidPasswordChar(ch); } #endregion #region IncludePrompt public static readonly DependencyProperty IncludePromptProperty = DependencyProperty.RegisterAttached( "IncludePrompt", typeof(bool), typeof(MaskedText), new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnIncludePromptChanged))); public static bool GetIncludePrompt(DependencyObject d) { return (bool)d.GetValue(IncludePromptProperty); } public static void SetIncludePrompt(DependencyObject d, bool value) { d.SetValue(IncludePromptProperty, value); } private static void OnIncludePromptChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { MaskedText maskedText = GetInstance(d); if (maskedText != null) { maskedText.CreateProvider(); } } #endregion #region HidePromptOnLeave public static readonly DependencyProperty HidePromptOnLeaveProperty = DependencyProperty.RegisterAttached( "HidePromptOnLeave", typeof(bool), typeof(MaskedText), new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnHidePromptOnLeaveChanged))); public static bool GetHidePromptOnLeave(DependencyObject d) { return (bool)d.GetValue(HidePromptOnLeaveProperty); } public static void SetHidePromptOnLeave(DependencyObject d, bool value) { d.SetValue(HidePromptOnLeaveProperty, value); } private static void OnHidePromptOnLeaveChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { MaskedText maskedText = GetInstance(d); if (maskedText != null) { maskedText.SetText(); } } #endregion private static void Attach(DependencyObject d) { TextBox textBox = d as TextBox; if (textBox != null) { MaskedText maskedText = GetInstance(d); if (maskedText == null) { maskedText = new MaskedText(textBox); SetInstance(d, maskedText); } else { maskedText.CreateProvider(); } } } private static void Unattach(DependencyObject d) { MaskedText maskedText = GetInstance(d); if (maskedText != null) { maskedText.Dispose(); SetInstance(d, null); } } } Usage is quite simple. <TextBox Margin="2" local:MaskedText.Mask="00/00/0000" local:MaskedText.PromptChar="?"/> We're no longer using a MaskedTextBox, but instead we use attached properties and behaviors to make existing TextBox controls behave appropriately. With Styles, Templates, Decorators (check out the SpinDecorator by MindScape, for example) and attached properties and behaviors you can accomplish most of the things that you used to have to roll a new control for. In the end, what you get is usually a more reusable piece of functionality than what you could get through inheritance. For instance, the MaskedText class could be modified to support more than just a TextBox, giving you the same behavior for, say, a ComboBox, while inheritance limits how you can reuse the functionality. WPF sure is powerful. August 12 VisualStudio 2008 and .NET Framework SP1These are now available, and there's much new in here. I'm going to provide some links to the pertinent stuff. VS 2008 Service Pack 1:
VS 2008 Express Editions with Service Pack 1:
VS Team System 2008 Team Foundation SP 1: .NET Framework 3.5 Service Pack 1 Team Foundation Server A general description of the release can be found here. Here's a list of some of the new WPF features, with links to information about them.
There were some things left out of SP1 that we had been under the impression were "in the works". Amongst these were a DateTimePicker and a DataGrid. These did not make it into SP1, but it looks like we'll be getting them "out of band" prior to their inclusion in some future WPF update. The DataGrid has already been released to CodePlex in what's being called the "WPF Toolkit", with the promise of this being the mechanism for introducing several other WPF features "out of band". If I've missed any features, or if links are available for any of the features I didn't provide links for, let me know and I'll update this post. August 01 Randomize ExtensionI'm not sure how useful this is, but it's a fun little thought experiment at the very least. The goal here is to randomize the order of an IEnumerable<T> via an extension method. It's actually a rather simple one liner (well, two if you count the creation of the Random instance). This specific implementation isn't optimal, and it suffers all the same issues as the pseudo random number generator Random, since it's built on that. It does illustrate the power of functional programming and LINQ, though. public static class RandomExtension { public static IEnumerable<T> Randomize<T>(this IEnumerable<T> self) { return self.OrderBy(i => random.Next()); } private static Random random = new Random(); } Now you can easily iterate randomly over any enumerable. LINQ/functional programming is quite fun, IMHO. Edit 8/28: Greatly simplified the implementation of this extension method. The previous implementation was clever, but just not as efficient. Looking at how simple this implementation is, you could argue there's little reason to wrap it up in an extension method. July 28 Attached Read-only Collection Dependency PropertiesOh wow! This is one I've been wanting almost since the day I started learning WPF. It seemed like such a huge thing missing from WPF. How in the heck do you manage to make an attached read-only dependency property with a collection type? At first blush, many of you are thinking "What's the big deal? Attached read-only dependency properties are clearly documented and easy to implement." Very true. But if you're thinking that, you're glossing over the critical bit here: the type of this property is supposed to be a collection. "So what?" is your response? Well, smarty, how do you initialize that property? See, if we rely on the dependency property's "default value" mechanism, since this is an attached property every instance will have that value. Now, what you need to be able to do here is lazily create the collection on first access. However, WPF doesn't provide you with any callbacks for retrieving the value, only for setting the value, and the XAML parser bypasses the CLR "get wrapper". So, there's no place to lazily instantiate the collection. I searched high and low for a solution here. I even talked directly with several Microsoft WPF developers. I think I may have even made an official request for an event to hook into for this purpose. In the end, though, I was left wanting. That is, until today. John Gossman e-mailed me today with a solution to this problem that works with the current WPF system. Holy smokes! The key to his solution lies with the fact that I didn't fully understand WPF (because this something that's implied and not spelled out in the documentation). Remember when I said the XAML parser bypasses the CLR "get wrapper"? Well, that's not always true. It seems that if you register the dependency property with a "name" that differs from the CLR name, the XAML parser will call your CLR "get wrapper" instead of bypassing it and going directly to GetValue. This may not make a lot of sense with out an example. Here's such an example (I'm not including the code for the Foo type, as it's not really relevant): public class FooCollection : ObservableCollection<Foo> { public static readonly DependencyPropertyKey InstancePropertyKey = DependencyProperty.RegisterAttachedReadOnly("InstanceInternal", typeof(FooCollection), typeof(FooCollection), new UIPropertyMetadata(null)); public static readonly DependencyProperty InstanceProperty = InstancePropertyKey.DependencyProperty; private static void SetInstance(DependencyObject obj, FooCollection value) { obj.SetValue(FooCollection.InstancePropertyKey, value); } public static FooCollection GetInstance(DependencyObject obj) { if (obj == null) throw new ArgumentNullException("obj"); FooCollection foos = obj.GetValue(FooCollection.InstanceProperty) as FooCollection; if (foos == null) { foos = new FooCollection(); SetInstance(obj, foos); } return foos; } } The key above is the first parameter passed to RegisterAttachedReadOnly: "InstanceInternal". Since the name is different than the implied CLR name ("Instance"), the XAML parser will now call our GetInstance method, which does the lazy creation. With this in place, the following XAML code actually works (it doesn't do anything useful, mind you, as this isn't a meaningful example beyond illustrating the concept of lazily creating the read-only attached collection dependency property). <Window x:Class="WpfAttachedCollection.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ui="clr-namespace:WpfAttachedCollection" Title="Window1" Height="300" Width="300"> <ui:FooCollection.Instance> <ui:Foo Name="Bar"/> </ui:FooCollection.Instance> <Grid> </Grid> </Window> This is one case where the VisualStudio designer fails me. I get an error "The attachable property 'Instance' was not found in type 'FooCollection'." I'm not sure what the cause of the failure here is, but when I running the executable, it actually does work. A little more work and I can add a button that displays the added Foo objects in a MessageBox.Show, just to prove to myself that everything is working. Like I said, this isn't exactly documented in the MSDN. It's only implied. The relevant documentation given to me by Mr. Gossman is this:
Mr. Gossman assures me the behavior here won't change, because it's relied upon by too much existing code. So, maybe it will be documented in the future, but for now you can feel safe in make use of this "hack". I owe Mr. Gossman a huge thank you for providing this to me, and I'm very happy to provide a post here for future Googlers. Update: Looks like Mr. Gossman blogged about this as well. July 21 Rant: $%!#(* GridI'm a tad frustrated by the default templates used by VisualStudio and Expression Blend for WPF. The XAML always starts you out with a Grid. WTF? The Grid panel is the most inefficient of the panels. It's also not all that easy to use for most layouts. In other words, its the layout I would expect most developers to use the second least often (Canvas takes the bottom spot). Don't get me wrong. There's plenty of scenarios in which Grid is your best layout choice. However, there's even more scenarios where it's not. Rarely is it the panel I'll want my Window/Page to use by default. I almost always rip that Grid out of the generated XAML and replace it with either a DockPanel or a StackPanel. So why the harsh tone to this rant? After all, it's not that big of a deal to replace it, if it's not appropriate. The tone is harsh, because having Grid as the default panel generated by tools is leading the unwashed developers into some bad habits. Most developers don't understand why Grid isn't as appropriate as other panels. When they see it being used by default, they think it's the panel they should fall back on by default. This leads to an overuse of the panel. That really bothers me. How many times have you seen a Grid used when there's a single column and/or row, for instance? Sorry, this is partially a religious debate. The performance of a Grid isn't so bad as to matter most of the time. But I still have the hairs on the back of my neck stand on end the majority of the time I see a Grid used in WPF. The fact that the generated XAML encourages this doesn't help. Update: Here's a classic example. The recently released Prism contains a "view" called OrdersToolbar. The OrdersToolbar.xaml file contains some lovely code in which the default template's Grid element was left in the source. It's only child element is a horizontal StackPanel that really defines the layout for the user control. Remove the Grid and the resulting user control is functionally identical, with easier to understand XAML markup and theoretically better performance (yeah, the perf hit here is not noticeable to the user and probably very difficult to measure, so I don't want to make too much out of that, but it is still there). Maybe the template shouldn't include anything but the Window/Page/UserControl, but it certainly shouldn't include a Grid, IMHO. May 21 An object you can depend onSorry for that. I needed a catchy title ;). Well, now that I'm a WPF Disciple I should probably blog more about WPF. What's interesting though, is despite the fact that I'm now working on a WPF project, most of the coding I've been doing lately has not been UI related. So, not much to blog about WPF. Well, over lunch today my mind was wandering over the stuff I have worked on lately. Among them was the IoC container I've been tooling about with. One of the things I did with the container was to extend the functionality through extension methods. Worked great, but occasionally your extensions needed new state information that didn't exist on the object. Well now, this ties back to WPF after all. See, there's this concept in WPF that directly addresses this. It's called an "attached property". An attached property is a property associated with an object instance, but the backing store isn't the object itself. This is accomplished through DependencyObject and DependencyProperty classes. A DependencyObject has GetValue() and SetValue() methods that take a DependencyProperty as a parameter, and get/set values for that dependency property through some magical backing store that's not actually part of the object (basically the storage is some giant HashMap). An attached property is a special type of DependencyProperty that allows you to attach values to any DependencyObject, not just the object on which the DependencyProperty is defined. So, by turning my ObjectContainer into a DependencyObject, I can allow extension methods to maintain their own state information on the container they are extending! Such a simple concept, and yet it didn't dawn on me to do this in my initial implementations. I rolled all of the necessary plumbing to track associated external state information by hand, and did it every time I needed new state information in a new extension method. What a waste of time and effort! :) I think I'm starting to fall in love all over again with DependencyObjects. I've avoided using them anywhere but at the UI layer. I thought it was wrong to bring in all of this baggage at the data/domain layer. After all, INotifyPropertyChanged gave me everything I need at that level, and Don Box agreed with me. Well, now I think Don and I were wrong. You see, even at the domain layer, it's not that unusual to need an "extension mechanism". Some way for a known domain object to be extended with more features under certain circumstances. We've all rolled specialized mechanisms for allowing this in the past. However, with a DependencyObject this functionality is baked in, using a mechanism that's well supported by the rest of the framework. I used to think that dependency properties were too much of a PITA to deal with unless you absolutely had to... but I think I'm changing my mind. The flexibility and functionality you gain by deriving from DependencyObject is probably enough to outweigh the development cost, especially if you use a good set of snippets. I've done a lot of programming in dynamic languages. The ability to add functions and data to an existing object always proved invaluable in those languages. Of course, the lack of compile time checks on all of that made maintaining those code bases a bit of a PITA. It's kind of exciting to realize now that I have most of the capability to do this but in a statically checked at compile time nature. May 02 Newest WPF DiscipleYou have GOT to check out the newest WPF Disciple. I'm not sure I can count all of the contributions he's made to the community. OCD - Oh is that Code Disgusting!This is a little rant. If you don't like those, move along. I think coders have been slowly becoming lazier, and now it's really started to get on my nerves. I've seen SOOO much public code lately that is just frightening! I'm not referring to the algorithms or other technical aspects of the code here, but rather to the lack of formatting. I'm not starting a religious war here. I don't care if you put your opening brace on the same line or the next. I don't care if the braces are indented or not. I don't care if you use tabs or spaces. I don't care if indents are 3 spaces or 12. I don't care if you mark members with an "_", an "m_" or any other wart. (Well, to be fair, I do have opinions on all of those, I just don't care if your opinion differs.) No, what I care about is consistency. It's the lack of consistency that I've been seeing lately that's lead to this rant. In fact, it goes beyond a lack of consistency. Developers aren't even TRYING to make their code readable or maintainable. Want an example of what I'm ranting about? Check out the published code in this article on CodeProject. I don't even know how hard you have to work to get code this unformatted, considering all editors today at the very least auto-indent for you. VisualStudio will even indent pasted code that originally had a different indent properly. And one of my bigger pet peeves is with extraneous blank lines. A single blank line in logical places to call out sections of code is very appropriate. I use a lot of them in my own code. But in that first code snippet there's no consistency in the way blank lines are used, and I count at least 8 lines that have no purpose existing. Worse, some of those are consecutive. Are we really becoming to lazy to bother making our code look professional, instead of looking like it was mashed out on a keyboard by a monkey with 3 missing fingers? Disclaimer: I don't mean to pick on the author of that CodeProject article. I've not read the article yet, and it may be of high quality, ignoring this glaring issue. April 24 The "var" controversyThere's some blog buzz going on right now about the appropriateness of using the new C# "var" keyword. I first ran across the meme from Jean-Paul S. Boodhoo's blog, with this post. He later linked to a post by Ilya Ryzhenkov on the same subject. One of the responses on Ilya's blog read:
This response was quoting a post by Rob Conery. Let me first say, I have not read all of Rob's post (mostly because the formatting is so bad, it makes it hard to read the post, and I don't have the time to spend on the effort). Maybe this quote is taken out of context, so take what I say next with a grain of salt. This quote is utter hogwash. The "var" keyword produces no extra code. Prove it to yourself. public class Foo { } class Program { static void Main(string[] args) { Foo explicitFoo = new Foo(); var inferredFoo = new Foo(); } } The resultant IL that's generated is this. .method private hidebysig static void Main(string[] args) cil managed { .entrypoint .maxstack 1 .locals init ( [0] class Playground.Foo explicitFoo, [1] class Playground.Foo inferredFoo) L_0000: nop L_0001: newobj instance void Playground.Foo::.ctor() L_0006: stloc.0 L_0007: newobj instance void Playground.Foo::.ctor() L_000c: stloc.1 L_000d: ret } The code for the explicitly declared variable and the inferred though "var" variable is identical. Do NOT fear using "var" because of performance concerns, as there is none. With that out of the way, where do I fall in opinion on this subject? Well, reading the various posts in this meme, there seems to be two camps. I think both are extremes. The first extreme is the "Microsoft Camp".
I simply can't agree with this extreme viewpoint. Tell me how the following code can possibly be considered less readable for others? var inferredFoo = new Foo(); The other camp, which I'll call the Boodhoo camp, though I don't have proof that Mr. Boodhoo specifically takes this extreme point of view, believe that you should always use "var". I can't agree with that extreme either. Can anyone tell me what the type of the following declaration is? var current = Foo.Current; We can have arguments until we're blue in the face about how better naming would have prevented this confusion. I don't buy the argument, though. First, names aren't always under your control. Second, even with better naming it's still possible to find yourself in situations where you don't have enough type information available to you in situations like this. C# is still a strongly typed language, and knowing the exact type your dealing with is important. Relying on the IDE is a no go for me, and relying on naming isn't always possible. So, what do I think? Out of habit, I'm still not using "var" that frequently, but I see no harm in using it for your typical "new" statements like the first example. I don't know if I'll get into the habit of doing that or not, but I see no reason to try and talk anyone out of doing so. For other declarations like the second example, unless the type is anonymous, I'd probably favor the Microsoft guideline of not using "var" here. You can probably get away with it 80% of the time and I won't care, but that other 20% is enough reason for me to not recommend getting into this habit. Edit: From a reply to Ilya's post by "Simon" we get a list of rules much closer to what I think makes sense.
The last bullet point is the most controversial, but I can agree with it as long as developers are consciously considering the choice. For the rest, I can see no reason to recommend not using "var" in any of those situations.
|
|
||||
|
|