Table of Contents
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 followed 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?
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.
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 own functionality to any type without touching its 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.
Again a very unspectacular peace 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
...
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>();
}
wich 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 at 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.
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 that before, you can get more information
here. Important 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 care about 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
.
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
WeakReferences
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;
}
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 the idea how to use it for weak references. That gave my implementation an enormous performance boost. Thanks, Mike! To make it short,
the
RepositoryItemTargetKey
deligates the
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.
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
let 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!
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 to the TB.ComponentModel namespace in order to use it.
- 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 a Session should do fine.
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
UniversalTypeConverter
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.
If something is not supported by the framework, you will have to do it on your own
While using WeakReferences I had to handle them in my unit tests. That means, I had to control that they were collect 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. Therefor, you can call
GC.Collect()
as you can see it in some of my tests.
- 23 Oct 2011: uses a dictionary instead of a list; article and source updated
- 18 Oct 2011: Performance added
- 10 Oct 2011: bug fixing, source updated
- 3 Oct 2011: first version