What could be more preferable than simply stating X = Y in one place then having the rest of the work transparently handled on your behalf?
Expression trees were one of the many language features introduced with C# 3.0 way back in 2007. Integration with the compiler allows us to nonchalantly ask it to emit data that represents a line of code instead of going about its usual business of readying the code for execution only. This leads to a convenient convergence of the benefits of reflection and those of compiler-checked code. An example that is further described here is that we no longer must use a string literal to meta-identify a property; instead, we can use a lambda expression like o => o.SomeProperty
.
It took me much of this month of January 2013 to apply expression trees in a way that goes far beyond that simple property example. The result demonstrates not only the power of code as data, but also the applicability of the concept of data binding to coding in general instead of in the user-interface alone.
The project arose from the need to manage numerous interdependent properties on view-model and domain model classes. A property must be updated whenever one of the properties it depends on changes. Done on an individual bases, this would lead to a lot of event handlers. My undeviating aim to keep application code as succinct and declarative as possible led me to seek an alternative. What could be more preferable than simply stating X = Y in one place then having the rest of the work transparently handled on your behalf? Nothing, of course. So I create the following public interface and then implemented the guts to make it work:
/// Keeps a target's properties updated with values determined by corresponding expressions. Properties referenced within a value expression are monitored such that property-change notifications cause the target properties to be updated.
/// </summary>
/// <typeparam name="TTarget">The type of object on which properties will be updated</typeparam>
public class PropertiesUpdater<TTarget> : IDisposable
{
/// <summary>
/// Constructs a PropertiesUpdater instance for properties of the specified target.
/// </summary>
/// <param name="target">The object on which properties will be updated</param>
public PropertiesUpdater( TTarget target )
{
...
}
/// <summary>
/// Sets the specified property of target to the result of the specified value expression whenever the value of a property referenced within the value expression changes.
/// </summary>
/// <typeparam name="TProperty">The type of the property on target to be set</typeparam>
/// <param name="targetPropertyExp">The property on target to be set</param>
/// <param name="valueExp">The expression to be monitored for changes and evaluated as the target property's value</param>
/// <returns>The instance on which this method was invoked, to allow multiple invocations to be chained together</returns>
public PropertiesUpdater<TTarget> AddProperty<TProperty>(
Expression<Func<TTarget, TProperty>> targetPropertyExp,
Expression<Func<TProperty>> valueExp )
{
...
}
public PropertiesUpdater<TTarget> RemoveProperty<TProperty>(
Expression<Func<TTarget, TProperty>> targetPropertyExp )
{
...
}
public void Dispose()
{
...
}
}
I will present some usage examples to show the sort of advantage that PropertiesUpdater provides. The restaurant POS system I am now working on includes a Table domain object that represents the sort of table you sit at. Table’s OpenOrder property is bound in the user-interface and so its private setter raises a property-changed event for notifying the UI of changes. Table ’s constructor contains the following code for determining OpenOrder’s value:
.AddProperty(
t => t.OpenOrder,
() => DineInParts.Select( p => p.Owner ).SingleOrDefault( o => o != null && o.IsOpen )
);
PropertiesUpdater recognizes most LINQ-to-Objects methods and can interpret the lambdas passed to them. After calling AddProperty in the above example, each of the following causes the target Table.OpenOrder property to be updated on this: (1) changes to the Table.DineInParts collection property’s value on this, (2) adds/removes of the collection instance’s elements, and (3) changes to values of the Owner and IsOpen properties referenced via collection elements. Under the covers, PropertiesUpdater relies on the referenced objects implementing INotifyPropertyChanged and the referenced collections implementing INotifyCollectionChanged.
Next I will present two usages taken from the constructor of the order detail screen’s view-model in the restaurant POS system-
// Initialize these without causing the contact to be overwritten.
new PropertiesUpdater<OrderDetailsScreenPM>( this )
.AddProperty(
pm => pm.PhoneNumber,
() => domainItem.Contact is VerifiedCustomer ? ((VerifiedCustomer)domainItem.Contact).PhoneNumber : null
).AddProperty(
pm => pm.Name,
() => domainItem.Contact != null ? domainItem.Contact.Name : null
);
// Store a contact in the domain model that has the user-provided phone number and name.
new PropertiesUpdater<Order>( domainItem )
.AddProperty(
dm => dm.Contact,
() => PhoneNumber != null
// If a phone# is provided then lookup a verified contact; if not found then create one.
? (DataAccess.Instance.FindCustomer( PhoneNumber ) ?? new VerifiedCustomer { PhoneNumber = PhoneNumber, Name = Name })
// If only a name is provided then create an unverified contact.
: Name != null ? new Customer { Name = Name } : null
);
The first thing you may notice about this example is that the “one line” permitted for the value expression is actually spanning multiple lines. The “one line” limit just means the expression must not contain a semicolon. The expression can in fact be however complex is needed.
In the value expressions for the first instantiated PropertiesUpdater, notice the presence of property paths that navigate across more than one property. These are domainItem.Contact.PhoneNumber
and domainItem.Contact.Name
. What if Contact's value changes? The former value of Contact is no longer of interest (in particular, PhoneNumber's and Name’s values on the former contact), therefore the PropertiesUpdater stops observing the former contact. This is a similar situation as when an item is removed from a collection in which case the removed element is no longer observed. In the opposing case, PropertiesUpdater will begin observing a newly introduced value.
The Wikipedia article on Reactive programming provides a canonical example that is in fact PropertyUpdater’s primary purpose. So, I decided to call the namespace for holding PropertiesUpdater and its supporting classes “Qnomad.Reactive”, at least until someone who knows better tells me that’s an inappropriate classification. As for Microsoft’s Reactive Extensions (Rx), I didn’t see its connection to what PropertiesUpdater provides until I came across an interesting article that fills in the gap: Data binding in code using Reactive Extensions. When I first saw the title, I worried my month-long work on PropertiesUpdater may have been for naught. The single usage example that the article builds up to, however, falls far short of what PropertiesUpdater can accomplish. The explained functionality reminds me of the basic building block with which PropertiesUpdater observes referenced objects, which resembles Josh Smith’s PropertyObserver. Using Rx for this purpose, however, doesn’t seem necessary or even useful. This leaves me wondering how “reactive” Rx really is; although, I suppose it may just be reactive in a different sense. As with other CS concepts that become buzzwords, “reactive” is likely to go through a period of overuse before its actual meaning gets whittled down in the minds of the masses. Instead of waiting for that to happen, though, I would love to hear some clarification sooner.
Realizing the significant time expense of implementing PropertiesUpdater about a week into it, I checked if something similar already exists. I came across the WhenAny method of the ReactiveUI project, but it pales in comparison to what I ended up accomplishing with PropertiesUpdater. It wasn’t until after completing PropertiesUpdater and describing it in the above paragraphs that I finally found three projects — Bindable LINQ, Obtics, and Continuous LINQ — that appear to accomplish a similar feat. My initial impression, however, is that PropertiesUpdater can more easily accomplish the same with its mere two public methods and comparatively tiny implementation. Anyway, I’ll further explore these two alternatives later, if there is occasion.
No comments:
Post a Comment