Refactoring INotifyPropertyChanged Setters

477 views Asked by At

I am sick to death of writing INPC setters like this:

public string Label
{
   get {return _label;}
   set
   {
      if (_label == value) return;
      _label = value;
      NotifyPropertyChanged(() => Label);
   }
}

I'd like to refactor the setting of the field the same way I did INPC; I'd like to pass in the Expression<Func<T>> (and probably the backing field) into something like this:

public string Label
{
   get {return _label;}
   set
   {
      SetProperty(() => Label, ref _label, value); 
   }
}

... and here's the implementation I've come up with in the base class:

  public virtual void SetProperty<T>(Expression<Func<T>> expression, ref T field, T value)
  {
     if (Equals(field, value)) return;
     field = value;
     NotifyPropertyChanged(expression);
  }

... and it seems to work - it compiles, at least <grin />. My question is: am I missing something here? is passing by ref going to accomplish what I'm trying to do?

2

There are 2 answers

6
quetzalcoatl On BEST ANSWER

Yes, it will. You're still going to be a little sick of writing private backing fields and repetitive parts though:

private int _foo;
public int Foo { get{return _foo;}, set { SetProperty("Foo", ref _foo, value); } }

you can't easily escape from that.

INPC works based solely on three things:

  • needs a NAME of the property
  • needs to raise the event
  • and optionally needs to actually store the value, somehow, somewhere

The value-storage is actually not so important. It's important so that the property itself actually behaves, but it's not important for INPC itself. INPC is only about name+eventraising.

Your approach is OK, I'm often tempted to do the same. Writing INPCs implementation is .. soo boring. Technically, what you "invented" (sorry, you're not the first one, I saw at least a dozen similar implementations:) ) isn't really not much of a difference, you just extracted some common code to a method. Not rocket science, but still few lines saved. There is a small "expense" of not being able to tighty control when/how the event is raised. But not much of a pain though, as you can always write manual event-rising and custom conditions when needed.

However, there are some things worth noting:

  • if (Equals(field, value)) is a good start, but with custom objects it will may require you to override Equals and GetHashCode in many data classes, and it's not always a good idea, especially in WPF and bindings. When passing a Equals/HashCode-overriding dataobject to WPF you must take care and be prepared to see that sometimes the WPF will get confused and sometimes may not update bindings properly (i.e. it may fail to notice that an object was replaced with a new one and leave some bindings bounds to old instance). It's good to make an overload to SetProperty that will taking a IEqualityComparer<T> instead; cosmetics though
  • in old .Net versions, it was possible to automatically get the NAME from the callstack, but it was very expensive. That's for example why many frameworks (like i.e. XPO) deliberately ceased to do it at some point and required you to provide the name manually. However, since .Net 4.5, the "caller information" is much cheaper. See: this article on [CallerMemberName] and friends and you will be able to remove the "string propertyName". It will cost a bit, but it may be handy for you.
  • speaking of frameworks, please see Caliburn Micro and it's PropertyChangedBase class. You may find it very useful (Caliburn is available on nuget) or, at least, see the implementation. It uses above-mentioned caller information as default, and also has some nice features like boolean flag to temporarily disabling notifications (IsNotifying). Very useful in some cases.
  • There is also commercial PostSharp utility, that is able to .. automatically generate all INPC implementations. You attach the PostSharp util as a postbuild step, and it reads attributes from your properties, and if it finds out that some property is marked as to-be-a-INPC, then it rewrites IL of your assembly and adds the INPC implementation to it. What's the gain? [INPC] public int Foo {get;set;} and done. However, please note, PostSharp is commercial utility. You may be able to do such thing yourself if you find an IL weaver, probably based on Cecil from Mono. Or maybe you will find a free util for that too. I don't remember much more, sorry.

EDIT:

Now you mentioned it - you quoted SetProperty(()=> Label, somePocoClass.Label, value) which uses a expressions to find the name of property in a compile-safe way (no strings! refactor/rename works). If you decide to stick to ref, then with some extra work, you can get the ()=>Label expression, analyze it and extract the name, then transform&rewrite that into val => _label = val delegate, and cache that delegate per-field, and then use that cached delegate instead using ref parameter, resulting in: set{ SetProperty(()=>Label, value); }

That's almost as "cool" as it could ever get wit current C# specs. But: Such SetProperty gets really complex, transforming expressions isn't light but can be made almost one-time cost, but delegate cache is not "for free" and you have to cache them, because calculating transformed delegates onthefly is way too heavy. I've tried that, I've seen others trying that, and from my observations, most of people quits. And many coworkers see it as magic and won't try to touch that if something works not right. Cool, fun, but you shouldn't need C# gurus for debugging/patching simple feature like INPC.

EDIT2:

Even simpler way - get some T4 templates to generate the code for you. This is also very popular way to handle that. Or rather, was, like 3-4 years ago. I know some people around who really love T4. Somehow, I not. I felt like writing PHP or first versions of ASP-not-Net.. but don't care about my opinion here - see them and try them for yourself. Many people like it.

By the way, sorry for being chaotic - I've just remembered some bits about IL weaving:

3
Scott Chamberlain On

If you are using .NET 4.5 You can use the CallerMemberName attribute to get the caller, you then don't need to pass in a expression.

Here is the base method I use for INPC

public virtual bool SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = "")
{
   if (Equals(field, value)) 
       return false;
   field = value;
   OnNotifyPropertyChanged(propertyName);
   return true;
}

This is used like

public string Label
{
   get {return _label;}
   set
   {
      bool changed = SetProperty(ref _label, value);
      if(changed)
      {
          //Some other code you want to run only on change.
      } 
   }
}

One very nice feature of this pattern is it is compatible with ReSharper's NotifyPropertyChangedInvocator attribute so you can have automatic rt.Click -> "To property with change notification".