Introduction
Extension methods have been around for quite a long time in C# and .NET now. This allows a type to be 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 start 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;
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 has 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:
static readonly ExtensionProperty<object, int> Id = new ExtensionProperty<object,int>();
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 even more compactly 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 changes, 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 useful 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));
...
var viewModel = model.Get(ViewModelProperty);
Caution: Although extension properties seem convenient, 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} />
</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) {
values.Remove(instance);
values.Add(instance, value);
}
}
}
}
Memory Leakage?
The use of ConditionalWeakTable guarantees that the value stored in the extension property is garbage collected when there are no references left to the value or to 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 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 care, 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:
- C# Easy Extension Properties by Moises Barba
- "Extension Properties" Revised by Oleg Shio
- Connected Properties on CodePlex
History
- 27th March, 2015 - Initial version published