This post is a second follow-up to an earlier attempt to simplify creating dependency properties. In the previous follow-up I discussed unanswered questions about the WPF dependency property system which prevented me from continuing the implementation.
First of all, I would like to thank Einar Ingebrigtsen for helping me out on some of the following issues. He has a solution of his own inside the open source Balder project.
The answers to the questions:
“Does anyone know concrete examples of problems caused by not following the convention of naming the CLR property wrapper the same as the dependency property, or in other words, why is it of any importance?”
External tools may use this convention to access the dependency properties. I would argue these tools could be made smart enough to solve this without the convention, but since my proposed solution doesn’t require passing this name anymore at all, this is not an issue anymore.
“Why can I still use the dependency properties through XAML, without adding the field identifiers that end with the suffix ‘Property’?”
The XAML processor doesn’t use these identifiers directly, as the documentation on msdn might make you believe. However, again external tools, or future versions may follow this convention, so let’s assume it is required. Since I don’t own/use any of these tools I can’t verify this. Of real importance is the necessity for the properties to be registered in a static context. Luckily this makes the factory approach still a viable option.
The solution!
Taking all this new information into consideration, this is the best I could come up with:
public class FactoryExample : DependencyObject
{
public enum Property
{
Standard,
ReadOnly, // This becomes a readonly property.
Callback // This property has callbacks assigned to it.
}
private static readonly DependencyPropertyFactory<Property> m_properties =
new DependencyPropertyFactory<Property>();
public static readonly DependencyProperty StandardProperty =
m_properties[Property.Standard];
public static readonly DependencyProperty ReadOnlyProperty =
m_properties[Property.ReadOnly];
public static readonly DependencyProperty CallbackProperty =
m_properties[Property.Callback];
[DependencyProperty(Property.Standard, DefaultValue="foo")]
public string Standard
{
get { return m_properties.GetValue(this, Property.Standard) as string; }
set { m_properties.SetValue(this, Property.Standard, value); }
}
[DependencyProperty(Property.ReadOnly)]
public bool ReadOnly
{
get { return (bool)m_properties.GetValue(this, Property.ReadOnly); }
private set { m_properties.SetValue(this, Property.ReadOnly, value); }
}
[DependencyProperty(Property.Callback)]
public int Callback
{
get { return (int)m_properties.GetValue(this, Property.Callback); }
set { m_properties.SetValue(this, Property.Callback, value); }
}
[DependencyPropertyChanged(Property.Callback)]
public static void OnChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
{
...
}
[DependencyPropertyCoerce(Property.Callback)]
public static object OnCoerce( DependencyObject d, object value )
{
...
}
}
As you might notice, not too many things have changed since the first post, but besides the advantages mentioned earlier, there are a couple of new features.
- The owner type for the factory is automatically set to the outer class of the passed template parameter. You can still pass the type manually.
- Exceptions are (optionally) thrown when the conventions aren’t followed. Thanks to the exceptions I actually found out one of my CLR property wrappers was set to protected (public is required), indicating a design flaw.
- Behaviour of get and set is encapsulated inside the factory. (E.g. No need to know whether a dependency property is read only when using a private setter.)
- Safe for obfuscation. (Before ID.ToString() was used, where ID was the enum value. Now the property name is used and the enum value is solely used to link things together.)
If you are anything like me, looking at the code you might think “This can still be improved!“. Unfortunately it looks like we reached the limitations of C#. What follows are some failed ideas I considered.
Requirement for an enum:
Why specify an enum, right? We already have the properties, why not use those as ID’s? In Einar’s implementation an expression tree is used as a way to represent the property by using a lambda expression, returning the property itself. (E.g. o => o.Standard) Unfortunately, expression trees can’t be passed as an argument to Attributes, required by the factory. Why? My guess is, “No particular reason.”
AbstractDependencyObject:
Why not have an abstract class implement the factory by default? A default GetValue() and SetValue() method could be added, so “m_properties.GetValue(this, Property.Standard)” would be reduced to “GetValue(Property.Standard)”. Such an implementation would quickly turn out unusable as multiple inheritance isn’t supported.
Every property has almost the exact same implementation!:
Although macros can be nasty, I’d argue this would be a correct usage for them. But, ofcourse C# doesn’t support macros. The duplicate code was there before, at least it’s reduced and grouped together now.
So it looks like this is the end, …. or is it? Enter, Aspect Oriented Programming!
At first, a strangely unfamiliar name to me. Now, I can only dream of the following implementation.
[DependencyObject]
public class FactoryExample
{
[DependencyProperty(DefaultValue="foo")]
public string Standard { get; set; }
[DependencyProperty]
public bool ReadOnly { get; private set; }
[DependencyProperty]
public int Callback { get; set; }
...
}
By acquiring a license for PostSharp, or with a lot of free time on your hands by implementing it yourself, you could attempt an implementation just like that! If it whet your appetite, be sure to check it out, but for me, the road ends here.
Dependency Property Factory source code
For the latest version of this code, you can download my FCL Extension library. It’s located in the Whathecode.PresentationFramework assembly under the namespace Whathecode.System.Windows.DependencyPropertyFactory.