Hemme's Blog

Stories from behind the keyboard

  • RSS
  • Twitter
  • Linkedin

Recently I needed to write some test against an utility class. This class handles an unmanaged type and of course I didn’t want to use this unmanaged type in my tests. I wrote an interface to target the only members of the unmanaged type that were used by my utility class. It turned out that this contract was really simple, having as its only member an indexer:

public interface IIndexable<T>    
{     
       object this[T fieldname] { get; set; }     
}

What I was not aware of (…shame on me…) is that you cannot cast a type to an arbitrary interface if the type doesn’t implement the interface. For instance, let’s consider Dictionary<string, object>. It’s indexer is declared as

public object this[string key] {get; set;} 

It looks like Dictionary<string, object> implements IIndexable<string>... True?

False. That would be duck-typing… Indeed Dictionary<string, object> *could* implement my new interface, but it doesn’t, since when it was defined, it didn't implement it.

Since a posteriori interface implementation is not possible, let’s see if you can define a cast operator, in order to convert a Dictionary to an IIndexable. Well, in C# defining a cast operator towards interfaces is denied. There’s no way to do this, and maybe for an handful of plausible reasons. The following code won’t ever compile (in other words, I cannot write any supplementary code in order to support the following line):

var indexme = (IIndexable<string>) new Dictionary<string, object>();

Since I couldn’t define any conversion method, I decided to implement a kind of proxy (I call it proxy for literal reasons, even if it resembles to me more like a tunnel). A class implements IIndexable<T> and is internally plumbed to the getter and the setter of the other class (e.g. the Dictionary), thanks to Reflection. Moreover, I wrote an extension method to easily create an instance of the proxy.

Let’s start from the extension method:

public static IIndexable<T> AsIndexable<T>(this object source)    
{     
    var t = source.GetType();     
    var idx = t.GetProperty(     
        "Item",     
        new Type[] {typeof (T)});

    var get = idx.GetGetMethod();    
    var set = idx.GetSetMethod();

    return new Indexable<T>(source, get, set);    
}   
As you can see, the extension method looks for the indexer –  t.GetProperty("Item", …) – and passes its getter and setter to the constructor of the proxy class (Indexable<T>). This class is defined like this:

public class Indexable<T> : IIndexable<T>    
{     
        public Indexable(object innerObject, MethodInfo get, MethodInfo set)     
        {     
            _innerObject = innerObject;     
            _get = get;     
            _set = set;     
        }     
        public readonly object _innerObject;     
        public readonly MethodInfo _get;     
        public readonly MethodInfo _set;     
        public object this[T property]     
        {     
            get     
            {     
                return _get.Invoke(_innerObject, new object[] {property });     
            }     
            set     
            {     
                _set.Invoke(_innerObject, new[] { property, value });     
            }     
        }     
    }

The hearth of the class is the indexer member. As you can see, its accessors invoke the accessors of the actual class, so you can simply use it like in the following example:

var dict = new Dictionary<string,object>();

dict.Add("foo", null); /* Allowing subsequent safe assignment operations,
like dict["foo"] = something */

var indexable = dict.AsIndexable();

Assert.IsNull(indexable["foo"]); 

indexable["foo"]="bar";

Assert.AreEquals("bar", indexable["foo"]); 

Writing such a proxy let my write a better decoupled code, since I managed to define my utility class methods as dependant to an interface (IIndexable<T>) instead of an unmanaged class. Perhaps this approach has to be unvestigated further, for instance against garbage collection issues.

I hope any reader comments about this soon. Happy programming!

No comments: