Table of Contents
Introduction
We all know that there are no extension properties. Extension methods, yes - but no extension properties. If this has already been a problem to you, this
article might be a solution.
Of course, you should prefer the "clean" way and extend your types if you need new properties. But that is not always possible - there are sealed
classes within the .NET Framework or within third party assemblies. Or it just seems to be an overhead to extend a type for using that property in only one
case. To make it short, I will not discuss the pros and cons about extension properties - you can use them just as you like.
But I will give you an example: I had to implement a module where all kinds of types came in - maybe once, maybe twice, do not know how often. But I had to recognize an instance the second
time it came in. So I wanted to add a "ticket" to any instance. Something as following would be nice:
theIncomingReference.TicketID = new Guid();
theIncomingReference.TicketTime = DateTime.Now;
So I can do something like this:
if(theIncomingReference.TicketID != null) {return;}
Of course, without theIncomingReference
having implemented "TicketID
" or "TicketTime
" as a property!
Just in case I could benefit from others work, I searched the internet for extension properties. No direct support was found. I came across
Mixin. But that was not what I was looking for. You will have to use interfaces and factory methods - not as generic
as I wanted it to be. There was really no solution for me. So I did it my way (good old Franky) and came up with the UniversalTypeExtender.
At the end, my solution looks like this:
theIncomingReference.Extended().Dynamically().TicketID = new Guid();
theIncomingReference.Extended().Dynamically().TicketTime = DateTime.Now;
Or by using a concrete type as an extension:
theIncomingReference.Extended().With<Ticket>().TicketID = new Guid();
theIncomingReference.Extended().With<Ticket>().TicketTime = DateTime.Now;
Of course, you can read the properties, later on:
Guid ticketID = (Guid) theIncomingReference.Extended().Dynamically().TicketID;
Or more type safe:
Guid ticketID = theIncomingReference.Extended().With<Ticket>().TicketID;
This is it (good old Michael) - working on all reference types without setting up anything - no interfaces, no inheritance, no factory!
Interested in how it works?
Overview
Again, there is no support for extension properties out of the box. So we will have to emulate this. This is done by "binding" a reference
of a type (e.g., Ticket
) to the extended object (e.g., theIncomingReference
). This "binding" is stored and it is used again on
every request on the extended object. So, actually you work with properties of the "binding" object instead with properties of the extended object.
The trick is that you always get the same "binding" object instance for the same extended object. Sounds easy? Yes, but of course there are a few
things to take care of - like garbage collection. We will have a look at this, soon. But first, a class diagram showing the components of the UniversalTypeExtender
:
You can see the "binding" object in the class diagram. It is called RepositoryItem
and comes as a nested class within its related
ExtensionRepository
. A RepositoryItem
holds a reference to the extended object and the extensions of this object.
The ExtensionRepository
keeps track of these RepositoryItem
s. The UniversalTypeExtender
itself provides only one
extension method - Extended<T>()
. This method returns an Extender
for the object to extend. The Extender
in turn
manages the different ways of extending an object - for now, Dynamically()
and With<TE>()
. Dynamically()
returns
a real dynamic object (DynamicPropertyExtension
) - that is why you can call any property without setting up anything else.
With<TE>()
returns an instance of the specified type - that gives you type safe coding.
So, that was a short overview. Let's have a deeper look at each of these components.
UniversalTypeExtender
As already mentioned, all you have to do in order to extend an object is to call the extension method Extended<T>()
. That was really
obvious because extension methods are the only way to add our own functionality to any type without touching the code. By using the generic type T
,
we can control that extensions only work on reference types - that means classes. It would be problematical
on value types because they are
mostly given by value. But the concept behind the extender needs references as you can see later on. Extended<T>()
returns an
Extender
which gets a reference to the extended object. So the code looks really simple:
public static Extender<T> Extended<T>(this T extendee) where T : class {
Contract.Requires<ArgumentNullException>(extendee != null);
return new Extender<T>(extendee);
}
Also, the UniversalTypeExtender
holds an instance of an ExtensionRepository
by implementing the singleton pattern:
private static volatile ExtensionRepository mRepositoryShouldOnlyBeCalledFromProperty;
private static readonly object SyncRoot = new object();
private static ExtensionRepository Repository {
get {
if (mRepositoryShouldOnlyBeCalledFromProperty == null) {
lock (SyncRoot) {
if (mRepositoryShouldOnlyBeCalledFromProperty == null) {
mRepositoryShouldOnlyBeCalledFromProperty =
new ExtensionRepository();
}
}
}
return mRepositoryShouldOnlyBeCalledFromProperty;
}
}
For further information about this implementation, look here
or here. It just ensures that there is only one instance of an ExtensionRepository
created.
Extender
Again, a very unspectacular piece of code. An Extender
just holds a reference to the object to extend and provides a set of methods for the
different ways of extending this object:
public class Extender<T> where T:class {
private readonly T mExtendee;
internal Extender(T extendee) {
Contract.Requires<ArgumentNullException>(extendee != null);
mExtendee = extendee;
}
public dynamic Dynamically() {
return With<DynamicPropertyExtension>();
}
public TE With<TE>() where TE:class, new() {
return Repository.GetExtension<TE>(mExtendee);
}
}
As you can see, the real work is delegated to the repository. For that, the With<TE>
forces TE
to provide a parameterless
constructor because the repository has to create a new instance of TE
on its first request. Also, you can see that Dynamically()
just calls With<TE>
with a predefined type - DynamicPropertyExtension
, at which we will come back later on.
You might wonder why I introduced the Extender
instead of just implementing proper extension methods on the
UniversalTypeExtender
. I wanted the extension method Extended()
to be limited to reference types. So I had to define
Extended<T>()
with T
being limited to classes. You do not have to think about T
- just type
Extended()
. But if I had to define TE
(from With<TE>()
), too - like Extended<T, TE>()
- you
would have to specify T
on any call. I did not like that, that's all Consider the Extender
to be part of a tiny fluent interface.
But let's continue with the ExtensionRepository
...
ExtensionRepository
The repository holds a dictionary of RepositoryItem
s. As you have seen above, the Extender
calls GetExtension<T>()
:
public T GetExtension<T>(object forInstance) where T : class, new() {
return Get(forInstance).GetExtension<T>();
}
which in turn calls Get<T>()
:
public RepositoryItem Get<T>(T forInstance) where T:class {
lock (mSyncRoot) {
return GetOrNew(forInstance);
}
}
This method locks the repository in order to ensure that only one thread has access to the inner item dictionary which is used in the called
GetOrNew
method:
private readonly Dictionary<RepositoryItemTargetKey, RepositoryItem> mItems = new
Dictionary<RepositoryItemTargetKey,RepositoryItem>();
private RepositoryItem GetOrNew(object forInstance) {
RepositoryItem item;
if (mItems.TryGetValue(new RepositoryItemTargetKey(forInstance), out item)) {
return item;
}
return NewItem(forInstance);
}
private RepositoryItem NewItem(object forInstance) {
RepositoryItem item = new RepositoryItem(forInstance);
mItems.Add(item.Key, item);
StartGCTimer();
return item;
}
Locking is used because the repository has to make sure that a call gets a "safe" view of the item dictionary. Imagine, a call would create a new item
because the requested item is not there, yet. And at the same time - before the new item is added to the dictionary - another call would do the same.
That would end up in two extension items for the same object! But that would bypass our concept - so we lock.
GetOrNew
looks for an item which belongs to the requested instance. If no item was found, it is created. Otherwise it is returned.
Garbage Collection
Before we will inspect the RepositoryItem
, we will have a look at garbage collection. This is the .NET way of freeing the memory of unused objects.
If you have not heard about it before, you can get more information here.
The important thing to know is that it removes "unused" objects. That means objects which are not referred any more. Now, have a look at our repository again: all created items
are stored in the inner item dictionary. That means, this dictionary is getting bigger and bigger. Because all these items are referred in one way - at least from the dictionary
itself - there is no automatic garbage collection from the framework. So we will have to take care of this on our own. And of course, we do! If you dig into
the ExtensionRepository
's code, you will notice that there is a timer which looks after garbage from time to time:
public void CollectGarbage() {
lock (mSyncRoot) {
List<KeyValuePair<RepositoryItemTargetKey,
RepositoryItem>< itemsToRemove = mItems.Where(item =>
item.Value.CouldBeCollectedByGC).ToList();
foreach (var item in itemsToRemove) {
mItems.Remove(item.Key);
}
if (mItems.Count == 0) {
StopGCTimer();
}
}
}
This method asks every item if it could be collected (how the item itself knows about this is described soon) and removes it, if so. Again, watch out for
locking! You can figure out when the timer starts and stops on your own. You can define the interval for garbage collection by setting
the CollectGarbageIntervalInMilliseconds
property of the UniversalTypeExtender
. Its default is 15.000 milliseconds.
Garbage collection seems to be just a tiny part of this article, but it really is an important part of the UniversalTypeExtender
.
RepositoryItem
Now let's inspect the RepositoryItem
. It holds a reference to an extended object and references to all extensions requested for this object. As you can see,
the reference to the extended object is stored as a WeakReference:
private readonly WeakReference mTargetReference;
public RepositoryItem(object target) {
Contract.Requires<argumentnullexception>(target != null);
mTargetReference = new WeakReference(target);
}
If it would be stored as a "normal" field - that means as a strong reference - it would never be collected by the garbage collection, as mentioned
above. But WeakReference
s give you the ability to hold a reference by allowing it to be collected if there is no other strong reference to it anymore.
So, you can check if a reference is still alive:
public bool CouldBeCollectedByGC {
get { return !mTargetReference.IsAlive; }
}
Fine! If an extension is requested, there are nearly the same things to do like described for the repository: locking, searching within the list, creating an instance, and so on:
private readonly object mSyncRoot = new object();
private readonly List<object> mExtensions = new List<object>();
public T GetExtension<T>() where T : class, new() {
lock (mSyncRoot) {
return GetExtensionOrNew<T>();
}
}
private T GetExtensionOrNew<T>() where T : class, new() {
object extension;
for (int i = 0; i < mExtensions.Count; i++) {
extension = mExtensions[i];
if (extension is T) {
return (T) extension;
}
}
return NewExtension<T>();
}
private T NewExtension<T>() where T : class, new() {
T newExtension = new T();
mExtensions.Insert(0, newExtension);
return newExtension;
}
RepositoryItemTargetKey
The RepositoryItemTargetKey
is used as key for the inner dictionary of the repository. It is necessary for generating a proper key for a weak reference, which
is a fundamental thing for the UniversalTypeExtender
. In my first version, there was a simple list holding the items.
Then, Mike Marynowski pointed me to the dictionary and gave me an idea of how to use
it for weak references. That gave my implementation an enormous performance boost. Thanks, Mike! To make it short, the RepositoryItemTargetKey
delegates
GetHashCode
and the Equals
method to the weak reference. For more details, I recommend to look into the code and to read Mike's comments below this article.
DynamicPropertyExtension
As shown at the beginning, you can extend an object by calling theIncomingReference.Extended().Dynamically()
. Then, you can type any property name as you
want and set it to any value you like. Is it a miracle (good old Freddy)? If you know the
DynamicObject
from .NET 4, you
already know the answer, which - of course - is: no! The DynamicObject
lets you implement dynamic property access. There are special methods you can override,
building the getter and setter of your properties. In this case, the values are stored within a private list - of course with their property names:
private readonly Dictionary<string, object> mProperties = new Dictionary<string, object>();
public override bool TryGetMember(GetMemberBinder binder, out object result) {
return TryGetPropertyValue(binder.Name, out result);
}
public override bool TrySetMember(SetMemberBinder binder, object value) {
SetPropertyValue(binder.Name, value);
return true;
}
private bool TryGetPropertyValue(string propertyName, out object result) {
return mProperties.TryGetValue(propertyName, out result);
}
private void SetPropertyValue(string propertyName, object value) {
mProperties[propertyName] = value;
}
You should be aware of just working with an object type here - not very type safe, indeed! There is support for using an index as getter and setter, too.
But this is nearly the same. So, just look into the source, if you like. That's it! You made it through the whole story!
Using the code
The code makes use of Code Contracts and
FluentAssertions for testing. So you will have to install them - both for free - in order to compile and
test the source code. But the final DLL can be found in the bin folder - ready to use. Just reference it and distribute it with your final binaries.
The UniversalTypeExtender
comes as an extension method. You will have to reference the TB.ComponentModel
namespace in order to use it.
Limitations
- As pointed out, it just works for reference types.
- Extensions you want to use by the
With<TE>()
method will have to implement a parameterless default constructor.
- As you have seen, the objects are not touched in any way. The extensions are just stored within a static list. That means
that, e.g., serialization will not work. So, putting an extended object into the ViewState of an ASPX page - in hope
of getting the extensions back on a postback - will not work, of course. Whereas storing an object in the Session should do fine.
Performance
Just for interest, I did a little setup for testing the performance. This test was done on a Core Duo 2.67 GHz with 3 GB RAM. During the test, 10,000 objects
were created and each of them was extended. Then these 10,000 objects were looped again and the extension was read for each. The garbage collection of the
UniversalTypeExtender
was called during the test. The average duration of the test was about 0.8 seconds - for 20,000 calls total! Just to compare: my first
implementation - working with a list instead of a dictionary - took 8.5 seconds for the same test.
Conclusion
If something is not supported by the framework, you will have to do it on your own .
Points of interest
While using WeakReferences, I had to handle them in my unit tests. That means, I had to control that they were collected by garbage collection so that I could
verify some actions within my code. Normally, you could not - and you should not! - control the time of collecting by yourself. You can trust the runtime - it knows best
when to do this. But for testing, you will have to control it. Therefore, you can call GC.Collect()
as you can see in some of my tests.
Sacha Barbar pointed me
to the ConditionalWeakTable
class which is a kind of dictionary and implements the functionality
I needed. This means, storing objects as weak references and garbage collecting of these. This seems to be the best solution and maybe I will change it in future.
Thanks to Sacha for this great information.
History
- 5 Jun 2013: Typo fixed
- 4 Nov 2011: Mentioned the
ConditionalWeakTable
class in the Points of interest section.
- 23 Oct 2011: Uses a dictionary instead of a list; article and source updated.
- 18 Oct 2011: Performance section added.
- 10 Oct 2011: Bug fixing, source updated.
- 3 Oct 2011: First version.