Extension Properties - CodeProject

:

Introduction

Extension methods have been around for quite a long time in C# and .NET now. This allows a type to extended with new methods without changing the type itself. Most frequent use of this is probably the LINQ extension methods for collections, for instance Where. When we have the possibility to extend a type with methods, developers naturally starts to ask for the possibility to extend types with new properties as well, for instance to store data associated with an object:

myInstance.MyExtensionId = 123; // Syntax not yet supported

  However, this syntax is not supported yet in C# or VB. Neverless, various solutions for getting the functionality for extension properties have been proposed, for instance here at The Code Project (see References below). In this article I present my own solution which have the following features:

  • lightweight implementation (simple wrapper around ConditionalWeakTable)
  • not using string as keys for property names, increasing and reducing risk of run-time time errors due to typos and after refactory.
  • type-safe using generics, both for property type and in the selection of what types that can be extended.
  • can store value types (but cannot extend value types)
  • property change notification (optional)
  • support for read-only /  lazy-initialized extension properties
  • basic one-way XAML/WPF binding support.

Using the code

Let us directly start with an example how to declare and use an extension property:

// Declare the extension property once
static readonly ExtensionProperty<object, int> Id = new ExtensionProperty<object,int>();

// ... and then use it like this:
MyObject testInstance = new MyObject();
testInstance.Set(Id, 123);

int id = testInstance.Get(Id);

Above we first declare a strongly typed extension property called Id of type int. In this case we specify that it can be used for any instance derived from Object. Then we set it and retrieve it for an object using the extension methods. If you compare this with an attached property you can see some similarities.   

In contrast to many other solutions for Extension properties I have seen, we do not use any literal strings to specify the name of the extension property. This is refactoring (renaming) friendly and reduces the risk of difficult to find problems due to typos.

We cannot get closer to the real property syntax than this. However, we can actually write it event more compact by using the indexer syntax:

Id[testInstance] = 123;

var id = Id[testInstance];

Here it becomes clear that ExtensionProperty is similar to an ordinary dictionary. However, even though this is stored in a static field only weak references are held to the entries, so that memory can be reclaimed during garbage collection when there are no other reference to the entries.

Change notification

If you want to get notified whenever the value of an extension property change you can register a handler for the Changed event:

Id.Changed += OnIdChanged;

...

private static void OnIdChanged(object instance, EventArgs arg)

{

     Debug.Print(String.Format("Id for {0} changed to {1}", instance, Id[instance]);

}

Lazy-initialized extension properties

If you want to have an extension property generated for you when it is requested you can use the LazyProperty. This can be usuful if you want to have a view-model, ICommand or something else associated with another object.

public static readonly LazyProperty<MyModel, MyViewModel> MyViewModelProperty = new LazyProperty<MyMode, MyViewModel>(model => new MyViewModel(model));

...

// Get viewmodel for a model object (created first time requested, and garbage collected when not referred to anymore)

var viewModel = model.Get(ViewModelProperty); 

Causion: Although extension properties seem convinient, overuse may hurt performance. As long as you only have a small amount of data to manage this might not be a problem.

Usage in WPF/XAML binding

To allow simple use in WPF/XAML binding scenarios I show in the attached source code how to extend ExtensionProperty to be used as a value-converter returning the value for the extension properties. This allows us to use it like this :

<DataTemplate DataType="{x:Type models:MyModel}" >

   <ContentControl DataContext="{Binding Converter={x:Static viewModels:ViewModels.MyViewModelProperty}}" >

       <TextBlock Text={Binding FullName} />  <!-- FullName is a property of the view model -->

   </ContentControl>

</DataTemplate>

Points of Interest

Simple basic implementation

The code-snippet below show the basic simple implementation of ExtensionProperty. In the attached source code you can see that we added some more methods and a base class to derive LazyProperty from, but in many scenarios the implementation shown here should be sufficient:

public class ExtensionProperty<TInstance, TProperty>  where TInstance : clas
{
    private readonly ConditionalWeakTable<TInstance, object> values = new ConditionalWeakTable<TInstance, object>();
    
    public TProperty this[TInstance instance] {
        get {
            object value;
            if (values.TryGetValue(instance, out value) == false) {
                return default(TProperty);
            }
            return (TProperty)value;
        }
        set {
           lock (values) { // consider removing lock if you never going to use if from multiple concurrent threads.
               values.Remove(instance);
               values.Add(instance, value);
           }
           // Change noticification comes here.
       }
    }
}

Memory leakage?

The use of ConditionalWeakTable guarantees that the value stored in the extension property is garbage collected when there is no references left to the value or to the the owner object (key). 

As pointed out in some forum, even if the value and key has been collected, the ConditionalWeakTable still may not reclaim all the memory allocated for its internal storage tables. So for instance if you have attached 100,000 extension property values and then then have removed all references, the ConditionalWeakTable still seems to keep a internal storage with room for at least the same number of entries. This memory is reused if more new extension property values are set, but all memory is not reclaimed until the application (domain) is unloaded. In general, this shall not be a problem. For those who cares I added a ClearAll method to the extension property classes that clears all property values and releases the reference to the ConditionalWeakTable so that all memory for that can be reclaimed.

References

I have not exhaustedly searched for other solutions, but here are some other implementations of Extension Properties:

  1. C# Easy Extension Properties by Moises Barba
  2. "Extension Properties" Revised by Oleg Shio
  3. Connected Properties on CodePlex

History

  • March 27, 2015 - Initial version published.