Donnerstag, 2. Mai 2013

A nice new feature for your ViewModel base class

Back in June 2010 I blogged about a View Model base class for WPF and Silverlight. With slight modifications I still use it today when I have to deal with MVVM.

With .NET 4.5 however, Microsoft added an new feature to the .NET Framework, that can make writing View Models even sweeter: the [CallerMemeberName]-Attribute.

Just a short recall: the view model base class uses two Methods Get and Set to manage property values. In your view model you can write something like

public class MyViewModel : ViewModelBase
{

  public string Name
  {
    get { return Get(() => Name); }
    set { Set(() => Name, value); }
  }

}

This way you get strongly typed properties in your view model, but you get rid of the backing field. And the base class takes care about change notifications via INotifyPropertyChanged. But to many people the lambda expression () => Name was confusing and hard to understand.Would it not be sweet if we could derive the calling property name from the context?

This was possible ever since by using the StackFrame class like this:

var stackTrace = new StackTrace();
var frame = stackTrace.GetFrame(1);
var callerName = frame.GetMethod().Name;

But this approach seems a bit cumbersome and has performance penalties, becuase building up the stack trace is an expensive operation. And for a property setter the the delivered method name is like "set_propertyName" - so additional work is needed to fiddle out the pure method name.

Thing get more handy with the new [CallerMemeberName] attribute. You can place it on a methods string parameter and this parameter will be filled with the name of the caller. Bummer. So your OnPropertyChanged-Method can look like this:

protected void RaiseNotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
  if (PropertyChanged != null)
    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}

This can be incorporated easily in our view model base class. It will look like this (a lot of stuff is ommited for brevity):

public class ViewModelBase : INotifyPropertyChanged
{
  public event PropertyChangedEventHandler PropertyChanged;

  private Dictionary<string, object> _value = new Dictionary<string, object>(); 

  protected T Get<T>([CallerMemberName] string callerMemberName = "")
  {
    if (!_value.ContainsKey(callerMemberName))
      return default(T);

    return (T)_value[callerMemberName];
  }

  protected void Set<T>(T value, [CallerMemberName] string callerMemberName = "")
  {
    if (!_value.ContainsKey(callerMemberName))
      _value.Add(callerMemberName, value);

      _value[callerMemberName] = value;

    RaiseNotifyPropertyChanged(callerMemberName);
  }

  protected virtual void RaiseNotifyPropertyChanged(string propertyName)
  {
    if (PropertyChanged != null)
      PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
  }
}

We still have our methods Get / Set around, but they get an additional optional parameter for the caller member name. With this at hand, our above property example looks like this:

public class MyViewModel : ViewModelBase
{

  public string Name
  {
    get { return Get<string>(); }
    set { Set(value); }
  }

}

The one thing to do now, is explicitly stating the return type in the Get()-Method, because it can no longer be derived from the lambda expression. But you never get anything for free, right? Another downside is, that the attribute is not available in Silverlight.

It's a kind of magic - impress your colleagues with this one :-)


Keine Kommentare:

Kommentar veröffentlichen