When doing MVVM I find that much of the time putting together the view model is spent creating properties that look like the following:
private string _userName; public string UserName { get { return _userName; } set { if (_userName != value) { _userName = value; RaisePropertyChanged("UserName"); } } }
Especially while just banging out a proof-of-concept, this seemed pointless. Well finally I have a solution to the tireless adding of properties.
Consider the following example:
<Window x:Class="DynamicTest.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <StackPanel> <TextBlock Text="User Name"/> <TextBox Text="{Binding UserName}" Name="txtUserName" /> <TextBlock Text="Password" /> <TextBox Text="{Binding Password}" Name="txtPassword" /> </StackPanel> </Window>
Typically you would need to create a view model the resembled the following:
public class ViewModel : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private string _userName; public string UserName { get { return _userName; } set { if (_userName != value) { _userName = value; RaisePropertyChanged("UserName"); } } } private string _password; public string Password { get { return _password; } set { if (_password != value) { _password = value; RaisePropertyChanged("Password"); } } } private void RaisePropertyChanged(string propertyName) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } }
Writing these types of view models can be time consuming. Presenting the DynamicViewModel, a simple class that extends DynamicObject to allow any property to be bound and used. With the dynamic view model you simply drop it in and those properties will be created as they are needed.
Though very magical and wonderful please be aware that THERE IS A REDUCTION IN PERFORMANCE WHEN USING THE DYNAMIC VIEW MODEL. More on that later.
Lets look at the implementation:
1: public class DynamicViewModel : DynamicObject, INotifyPropertyChanged
3: public event PropertyChangedEventHandler PropertyChanged;
5: private readonly Dictionary<string, PropertyInfo> _values = new Dictionary<string, PropertyInfo>();
7: protected dynamic Self
12: public override bool TryGetMember(GetMemberBinder binder, out object result)
14: PropertyInfo propInfo;
15: if (_values.TryGetValue(binder.Name, out propInfo) == false)
17: _values[binder.Name] = propInfo = new PropertyInfo(binder.ReturnType);
19: result = propInfo.Value;
23: public override bool TrySetMember(SetMemberBinder binder, object value)
25: bool raisePropertyChanged = false;
26: PropertyInfo propInfo;
27: if (_values.TryGetValue(binder.Name, out propInfo) == false)
29: _values[binder.Name] = propInfo = new PropertyInfo(binder.ReturnType);
30: raisePropertyChanged = true;
33: if (propInfo.ReadOnly)
34: throw new InvalidOperationException(string.Format("Cannot set ReadOnly property {0}", binder.Name));
36: if (propInfo.Value != value)
38: propInfo.Value = value;
39: raisePropertyChanged = true;
42: if (raisePropertyChanged)
43: RaisePropertyChanged(binder.Name);
47: protected void SetReadOnlyProperty(string propertyName, object value)
49: if (_values.ContainsKey(propertyName))
50: throw new InvalidOperationException(string.Format("Property {0} already exists", propertyName));
51: _values[propertyName] = new PropertyInfo(value) { ReadOnly = true };
54: protected void RaisePropertyChanged(string propertyName)
56: if (PropertyChanged != null)
57: PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
60: private class PropertyInfo
62: public object Value { get; set; }
64: public bool ReadOnly { get; set; }
66: public PropertyInfo(Type type)
68: if (type.IsValueType)
69: Value = Activator.CreateInstance(type);
72: public PropertyInfo(object value)
The basic idea is pretty simple. The DynamicViewModel maintains a dictionary (line 12) of "Properties" that is contains, keyed by the name of the property. The private PropertyInfo class (line 67) contains the value of the property and a Boolean indicating if the property is read-only.
The read-only concept does differ from the C# readonly keyword, in that a read-only property in the DynamicViewModel can only be set once, and must be set prior to any caller attempting to access it.
The keys to this object functioning come from the two overridden methods TryGetMember (line 19) and TrySetMember (line 30). When using a dynamic object the DLR will first attempt to find a static reference to the member being accessed or set. If one is not found then the appropriate Try(Get/Set)Member method is invoked. In these cases the DynamicViewModel first checks to see if a member with the given name exists, and if not it creates one with the default value for the given type. If the member exists and the value is changing it will raise the PropertyChanged event.
Now for a bit about performance. In my tests comparing the typical view model presented above to the dynamic view model the dynamic view model was ~5% slower than the typical view model. With that said I still believe this is a valuable class that I intend to use on any view model where optimum performance is not needed.
Properties do not need to be added to the view model, saving time and code.
Because it is dynamic the typical issues arise from not having compile time checking. This can result in typos not being caught until run time.
Slower performance. A ~5% decrease in performance when accessing members can be expected.