Click here to Skip to main content
Click here to Skip to main content

"Extension Properties" Revised

By , 2 Aug 2013
Rate this:
Please Sign up or sign in to vote.

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 however now it is apparent that it is 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 so 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 the 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 (AttchaedProperties) 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 too 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 the discussion...

History

  • First release

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Oleg Shilo
Technical Lead
Australia Australia
I was born in Ukraine. After completing the university degree worked there as a Research Chemist. Last 18 years I live in Australia where I've got my second qualification as a Software Engineer.
 
"I am the lucky one: I do enjoy what I am doing!"

Comments and Discussions

 
QuestionGood PinmemberFatCatProgrammer2-Aug-13 4:31 
AnswerRe: Good PinmemberOleg Shilo2-Aug-13 4:58 
QuestionSome Concerns Pinmemberprash_522-Aug-13 1:10 
AnswerRe: Some Concerns PinmemberOleg Shilo2-Aug-13 1:33 
AnswerRe: Some Concerns PinmemberOleg Shilo2-Aug-13 3:07 
GeneralRe: Some Concerns Pinmemberprash_522-Aug-13 5:44 
QuestionHi PinmemberJeff Varszegi23-Jun-13 13:55 
AnswerRe: Hi PinmemberOleg Shilo23-Jun-13 15:01 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140415.2 | Last Updated 2 Aug 2013
Article Copyright 2012 by Oleg Shilo
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid