Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Extension Properties Revised

0.00/5 (No votes)
23 Mar 2015 1  
The extremely simple implementation of generic Extension Properties

Background

C# offers many features of the functional languages. One of them is the Extension Methods. This fantastic feature allows solving some non-trivial problems in very elegant ways.

However, I always felt that the type extensibility model was not complete without Extension Properties. Apparently, I am not alone. Now and then, I see the articles/solutions for mimicking Extension Properties. Initially, I thought that Microsoft would eventually deliver a proper Extension Properties implementation but now it is apparent that it's very unlikely.

To be technically accurate, XAML Attached Properties allows extending types with the property-like dynamic "members". However, the Attached Properties implementation is relatively heavy, too XAML specific and the usage pattern isn't so great neither.

Thus the presented solution is an attempt to implement generic Attached Properties based on the extension methods syntax.

While hoping for a proper solution from Microsoft, I developed and used my own, which achieves the desired behavior. Though it is lacking the syntactical sugar possible only by truly extending the C# syntax, it served me very well and I decided to share it.

The trigger for this article was another article on the very same subject:

It is a very good solid article. The author got my 5. He explained very well the objective he was trying to achieve and provided a robust working solution for the problem. However, I felt that his solution was a bit over engineered and practically the same behavior could be achieved with a much simpler implementation.

The Solution

How It Works

The solution is straight forward. It is built around the dictionary, which associates the instance of an object with a collection of the named values (key/pair). The dictionary is hosted by the AttachedProperies static class, which allows setting and getting named values to the instance of the object via Extension Methods:

static class AttachedProperies
{ 
    public static Dictionary<WeakReference, Dictionary<string, WeakReference>> ObjectCache;
    public static void SetValue<T>(this T obj, string name, object value)...
    public static T GetValue<T>(this object obj, string name)... 

ObjectCache references the objects with WeakReferences for avoiding memory leaks. The name of the class (AttachedProperties) is deliberate as it mimics the XAML Attached Properties. The API relies on extension methods so the usage pattern can be as simple as follows:

var animation = (Storyboard)FindResource("Storyboard1");
animation.SetValue("StartTime", DateTime.Now);
animation.Begin();
.....
void OnStoryboard_Complete(object sender,....)
{
    var animation = (Storyboard)sender;
    DateTime startTime = animation.GetValue<DateTime>("StartTime"); 

Of course, using string literals isn't the cleanest approach. Thus, I would recommend to group properties by the target type in the static classes containing extension methods. This allows strongly typed and readable syntax:

static class StoryboardExtensions
{
    public static DateTime GetStartTime(this Storyboard obj)
    {
        return obj.GetValue<DateTime>("StartTime");
    }
 
    public static void SetStartTime(this Storyboard obj, DateTime value)
    {
        obj.SetValue("StartTime", value);
    }
} 

And the usage:

animation.SetStartTime(DateTime.Now);
...
DateTime  startTime = animation.GetStartTime(); 

The solution also solves an interesting problem - collecting the weak references to the already disposed objects. The routine which does this is the AttachedProperties.Collect method. Collect is to be invoked either explicitly or automatically depending on the AttachedProperties.MemoryManagementMode:

  • Progressive
    Collect will be called automatically every time when number of the "weakly-referenced" instances doubles (since the last Collect call).
  • GCSynchronized
    Collect will be called automatically when Garbage Collector collects unreferenced resources.
  • OnAllocate
    Collect will be called automatically with every AttachedProperties.SetValue<T>(...) call.
  • Manual
    Collect will be called explicitly from the host code.

That is it! This is roughly all about the solution. The source code can be found in the article downloadables (AttachedProperties_original.cs).

The presented solution despite some conceptual similarities has significant differences comparing to the other solution I mentioned in the introduction:

  • The implementation is much leaner (~150 lines of code).
  • The timer based garbage collection mechanism present in the alternative solution seems too simplistic to me. So I have implemented the event driven collection mechanism.
  • The presented solution deliberately does not offer any discovery mechanism. In my opinion, using the TypeDesciptorProvider in the alternative solution does not really bring any practical value. Thus I decided not to invest in this feature.

Revising the Solution

My original implementation was based on the WeakReference dictionary. However in .NET 4.0, there is a more suitable collection type for this - ConditionalWeakTable. This class is capable of fully automatic removal of all references to the instances no longer referenced anywhere else. Because there is no need for any memory management any more, the whole solution can be collapsed to the ~30 lines of code. The following is the final revised solution:

public static class AttachedProperies
{
    public static ConditionalWeakTable<object, 
        Dictionary<string, object>> ObjectCache = new ConditionalWeakTable<object, 
        Dictionary<string, object>>();
 
    public static void SetValue<T>(this T obj, string name, object value) where T : class
    {
        Dictionary<string, object> properties = ObjectCache.GetOrCreateValue(obj);
 
        if (properties.ContainsKey(name))
            properties[name] = value;
        else
            properties.Add(name, value);
    }
 
    public static T GetValue<T>(this object obj, string name)
    {
        Dictionary<string, object> properties;
        if (ObjectCache.TryGetValue(obj, out properties) && properties.ContainsKey(name))
            return (T)properties[name];
        else
            return default(T);
    }
 
    public static object GetValue(this object obj, string name)
    {
        return obj.GetValue<object>(name);
    }
}  

At the time of the first implementation, I was not aware of ConditionalWeakTable so I used a dictionary. This decision required me to solve the memory management challenges. Because the original solution demonstrates some interesting techniques, I decided to include it into downloadables anyway (AttachedProperties_original.cs).

Also, the original solution can be used under early versions of CLR (except for the GC events). However, if your target platform is .NET 4.0, then you should use the ConditionalWeakTable based solution (AttachedProperties.cs). It is simpler, better with the memory management and well... did I mention it is simpler?

Limitations

It is important to be aware of the limitations of the presented solution:

  • ConditionalWeakTable based solution can only be run on .NET v4.0.
  • ConditionalWeakTable based solution cannot be extended to support any sort of discovery mechanism. The reason for this is that ConditionalWeakTable does not support any browsing API as Dictionary does.
  • Support for value types as instances to attach the values to is problematic. This is a common limitation for all solutions of this sort.

Points of Interest

The generic Attached Properties is a great "assistance feature" for implementing loosely coupled architecture. They also allow bringing (when required) the stateful nature to the otherwise stateless extensibility model offered by Extension Methods. Which in turn brings Extension Methods up to the level when it is possible to implement what I would call "Safe Multiple Inheritance" in C# . Though it is another topic for discussion...

History

  • First release

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here