Attributes in .NET can be used to add metadata to language elements. In combination with reflection they can be put to use in plenty of powerful scenarios. A separate parser can process the data added and act upon the annotations in any way desired. A common example of this is a serializer which knows how to serialize an instance of a class based on attributes applied to its members. E.g. a NonSerialized attribute determines the member doesn’t need to be serialized.
In my previous post I promised I would show some interesting scenarios where the mandatory enum values in my solution, needed to identify different dependency properties (DPs), can be put to good use. It is not required to grasp the entire subject previously discussed, but it does help to know about DPs and their capabilities. Simply put, they are special properties (used by XAML) which can notify whenever they are changed, allowing to bind other data to them. They can also be validated (check whether a value assigned to them is valid or not) and coerced (adjusting the value so it satisfies a certain condition). I consider these capabilities to be metadata, describing particular behavior associated with a given property. After extensive tinkering I found a way to express this behavior in an attribute, but it wasn’t easy. Ordinarily WPF requires you to pass callback methods along when ‘registering’ the DPs, which implement the desired behavior.
Two solutions can be considered:
- The parser knows how to interpret the metadata and acts accordingly.
- The attribute implements the actual behavior itself, and is simply called by the parser.
Solution 1 is the easiest to implement, and is how attributes are used most often. Solution 2 however has a great added benefit. It allows you to apply the strategy pattern. Unlike solution 1, the parser doesn’t need to know about the specific implementation, but only the interface. Additional behaviors can be implemented and used without having to modify the parser. In contrast to simple metadata, an actual behavior is attached, hence I am dubbing this metabehavior. I will discuss the loopholes you have to jump through to achieve this in a next post. For now, consider the following examples to see how it could be used.
A regular expression validation of a dependency property can be used as follows:
[DependencyProperty( Property.RegexValidation, DefaultValue = "test" )] [RegexValidation( "test" )] public string RegexValidation { get; set; }
Coercing a value within a certain range, defined by two other properties can be used as follows:
[DependencyProperty( Property.StartRange, DefaultValue = 0 )] public int StartRange { get; set; } [DependencyProperty( Property.EndRange, DefaultValue = 100 )] public int EndRange { get; set; } [DependencyProperty( Property.CurrentValue )] [RangeCoercion( typeof( int ), Property.StartRange, Property.EndRange )] public int CurrentValue { get; set; }
The greatest thing about all this, is that new behaviors can be implemented by extending from ValidationHandlerAttribute and CoercionHandlerAttribute respectively, albeit with some added complexities due to the limitations of attributes which I will discuss later.