Friday I was discussing some of the new features in Visual Studio 2008 SP1 with some co-workers and one of them mentioned a new feature where VS 2008 will do symbolic renaming on all WCF Service Artifacts. This lead to a conversation on how the IDE understands properties and methods differently then their corresponding names in XML configuration files (WCF) and string literals (data binding). During the conversation I had a small light come on, regarding data binding and string literals.
With .NET 3.5 it is possible to do compile time checking for data binding using Lambda expressions. The idea is that instead of using a string to represent the name of the property you would pass in a Lambda Expression referencing that property. Here is a simple example using a business object property that raises PropertyChanged.
public class Person : INotifyPropertyChanged
{
private string _name;
public string Name
{
get { return _name; }
set
{
if (!object.Equals(_name, value))
{
_name = value;
OnPropertyChanged(() => Name);
}
}
}
private void OnPropertyChanged(Expression<Func<object>> property)
{
MemberExpression me = property.Body as MemberExpression;
if (me == null)
throw new ArgumentException("property.Body must be of type MemberExpression", "property");
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(me.Member.Name));
}
public event PropertyChangedEventHandler PropertyChanged;
}
The first benefit you get by doing this is that when you use the VS IDE refractoring tools to rename Name, the IDE will find Name in the Lambda expression and rename it there as well. It is also possible to create type safe implementation using a generic base class:
public class BusinessBase<TObject> : INotifyPropertyChanged
{
protected bool SetHelper<T>(Expression<Func<TObject, object>> property, ref T field, T value)
{
MemberExpression me = property.Body as MemberExpression;
if (me == null)
throw new ArgumentException("property.Body must be type MemberExpression", "property");
if (!object.Equals(field, value))
{
field = value;
OnPropertyChanged(me.Member.Name);
return true;
}
return false;
}
private void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
and it’s corresponding implementation:
public class Person : BusinessBase<Person>
{
private string _name;
public string Name
{
get { return _name; }
set { SetHelper<string>(p => p.Name, ref _name, value); }
}
}
Honestly, I am not crazy about the second implementation which requires inheriting from a generic class and giving it a concrete implementation, which locks you into always being BusinessBase<Person> when you’ve extended Person to be Employee. I will have a serious of blogs posts on some of these issues, with my Core series soon.
So anyway, enough with the business objects, lets look at databinding. Currently in .NET you create a binding between a control and an object using the following code:
this.firstTextBox.DataBindings.Add(new Binding("Text", this.personBindingSource, "First", true));
This loosely couples the Text property of the firstTextBox TextBox to the First property of some object, referenced by personBindingSource. Once the designer generates this code, it is not checked against the actually objects. Using Lambda Expressions the previous line of code could be implemented like:
this.firstTextBox.DataBindings.Add(
new ExpressionBinding<TextBox, Person>(c => c.Text, this.personBindingSource, p => p.First, true));
where the definition of ExpressionBinding extends Binding:
public class ExpressionBinding<TControl, TDataSource> : Binding
{
public ExpressionBinding(Expression<Func<TControl, object>> property, object dataSource, Expression<Func<TDataSource, object>> dataMember)
: this(property, dataSource, dataMember, false, DataSourceUpdateMode.OnValidation, null, string.Empty, null)
{ }
public ExpressionBinding(Expression<Func<TControl, object>> property, object dataSource, Expression<Func<TDataSource, object>> dataMember, bool formattingEnabled)
: this(property, dataSource, dataMember, formattingEnabled, DataSourceUpdateMode.OnValidation, null, string.Empty, null)
{ }
public ExpressionBinding(Expression<Func<TControl, object>> property, object dataSource, Expression<Func<TDataSource, object>> dataMember, bool formattingEnabled, DataSourceUpdateMode dataSourceUpdateMode)
: this(property, dataSource, dataMember, formattingEnabled, dataSourceUpdateMode, null, string.Empty, null)
{ }
public ExpressionBinding(Expression<Func<TControl, object>> property, object dataSource, Expression<Func<TDataSource, object>> dataMember, bool formattingEnabled, DataSourceUpdateMode dataSourceUpdateMode, object nullValue)
: this(property, dataSource, dataMember, formattingEnabled, dataSourceUpdateMode, nullValue, string.Empty, null)
{ }
public ExpressionBinding(Expression<Func<TControl, object>> property, object dataSource, Expression<Func<TDataSource, object>> dataMember, bool formattingEnabled, DataSourceUpdateMode dataSourceUpdateMode, object nullValue, string formatString)
: this(property, dataSource, dataMember, formattingEnabled, dataSourceUpdateMode, nullValue, formatString, null)
{ }
public ExpressionBinding(Expression<Func<TControl, object>> property, object dataSource, Expression<Func<TDataSource, object>> dataMember, bool formattingEnabled, DataSourceUpdateMode dataSourceUpdateMode, object nullValue, string formatString, IFormatProvider formatInfo)
: base((property.Body as MemberExpression).Member.Name, dataSource, (dataMember.Body as MemberExpression).Member.Name, formattingEnabled, dataSourceUpdateMode, nullValue, formatString, formatInfo)
{ }
}
Unfortunately, this implementation is not practical in WinForms since the current Visual Studio designer does not support lambda expressions for binding. I would be interested to see if the WinForms team is looking into improved implementations of data binding with the 3.5 technologies.