Even “More Static” Virtual Static Interface Methods in C#

The implementation of pseudo-static virtual methods I sketched in my previous post has one problem:

the implementation is currently not truly static since a StaticClass object is instantiated every time it is referenced

To fix this behavior, we need to cache the instances of the pseudo-static classes. Which immediately raises another question: Do we instantiate a pseudo-static class as a singleton for all classes that declare the pseudo-static class, or do we instantiate on a per-class level.

More complexity ahead:

public enum StaticClassInstantiation
{
    Singleton,
    PerClass
}

[AttributeUsage(AttributeTargets.Class, 
    AllowMultiple = false, Inherited = true)]
public class StaticClassInstantiationAttribute : Attribute
{
    StaticClassInstantiation inst;

    public StaticClassInstantiationAttribute(
        StaticClassInstantiation instantiation)
    {
        this.inst = instantiation;
    }

    public StaticClassInstantiation Instantiation { get { return inst; } }
}

The StaticClassInstantiationAttribute defines the instantiation policy of a pseudo-static class. The StaticHelper helper class (introduced in the previous post) will interpret this attribute, so we need to extend the class to handle the new semantics:

public static class StaticHelper
{
    static Dictionary<Type, object> singletons = 
        new Dictionary<Type, object>();        // index is static class
    static Dictionary<Type, object> classes = 
        new Dictionary<Type, object>();            // index is class

These dictionaries keep track of instances of pseudo-class, created as singletons or per-class.

CreateInstance() retrieves or creates and stores these instances:

    static Type GetStaticType(Type t)
    {
        var attrs = t.GetCustomAttributes(
            typeof(StaticClassAttribute), true);
        if (attrs.Length == 0)
            return null;
        return (attrs[0] as StaticClassAttribute).Type;
    }

    static object CreateInstance(Type objectclass, Type staticclass)
    {
        if (staticclass == null) return null;    // or throw NotImplemented

        var attrs = staticclass.GetCustomAttributes(
            typeof(StaticClassInstantiationAttribute), true);
        if (attrs.Length == 0 ||
            (attrs[0] as StaticClassInstantiationAttribute).Instantiation 
                == StaticClassInstantiation.PerClass)  // arbitrary default 
        {
            if (classes.ContainsKey(objectclass))
                return classes[objectclass];

            var c = staticclass.GetConstructor(Type.EmptyTypes)
                .Invoke(new object[] { });
            classes.Add(objectclass, c);
            return c;
        }
        else
        {
            if (singletons.ContainsKey(staticclass))
                return singletons[staticclass];

            var c = staticclass.GetConstructor(Type.EmptyTypes)
                .Invoke(new object[] { });
            singletons.Add(staticclass, c);
            return c;
        }
    }

Get() methods are adapted to call CreateInstance():

    public static object Get<T>()
    {
        var t = GetStaticType(typeof(T));
        return CreateInstance(typeof(T), t);
    }

    public static S Get<T, S>() where S: class
    {
        return Get<T>() as S;
    }

    public static object Get(object o)
    {
        var t = GetStaticType(o.GetType());
        return CreateInstance(o.GetType(), t);
    }

    public static S Get<S>(object o) where S: class
    {
        return Get(o) as S;
    }
}

We extend the sample of the previous post by adding a property to the sample interface and its implementing classes, and defining the instantiation policies using the new StaticClassInstantiation attribute:

public interface IStaticSampleClass
{
	void Hello();
	string Data { get; set; }
}

[StaticClassInstantiation(StaticClassInstantiation.Singleton)]
public class StaticSampleClass : IStaticSampleClass
{
	public void Hello()
	{
		Console.WriteLine("hello from " + this.GetType().Name);
	}
	public string Data { get; set; }
}

[StaticClassInstantiation(StaticClassInstantiation.PerClass)]
public class StaticSampleClass2 : IStaticSampleClass
{
	public void Hello()
	{
		Console.WriteLine("bye from " + this.GetType().Name);
	}
	public string Data { get; set; }
}

[StaticClass(typeof(StaticSampleClass))]
public class SampleClass
{
}

public class SampleClassEx : SampleClass
{
}

[StaticClass(typeof(StaticSampleClass2))]
public class SampleClass2 : SampleClass
{
}

public class SampleClass3 : SampleClass2
{
}

To test the implementation, we set and get the pseudo-static property Data. StaticSampleClass is declared as singleton, and StaticSampleClass2 as per-class:

StaticHelper.Get<SampleClass, IStaticSampleClass>().Data = 
    "set SampleClass";
StaticHelper.Get<SampleClassEx, IStaticSampleClass>().Data = 
    "set SampleClassEx";
StaticHelper.Get<SampleClass2, IStaticSampleClass>().Data = 
    "set SampleClass2";
StaticHelper.Get<SampleClass3, IStaticSampleClass>().Data = 
    "set SampleClass3";

Console.WriteLine("get SampleClass: " + 
    StaticHelper.Get<SampleClass, IStaticSampleClass>().Data);
Console.WriteLine("get SampleClassEx: " + 
    StaticHelper.Get<SampleClassEx, IStaticSampleClass>().Data);
Console.WriteLine("get SampleClass2: " + 
    StaticHelper.Get<SampleClass2, IStaticSampleClass>().Data);
Console.WriteLine("get SampleClass3: " + 
    StaticHelper.Get<SampleClass3, IStaticSampleClass>().Data);

As expected, the resulting output is:

get SampleClass: set SampleClassEx
get SampleClassEx: set SampleClassEx
get SampleClass2: set SampleClass2
get SampleClass3: set SampleClass3

Final note: the instantiation of the pseudo-static classes in this example is not thread-safe. Thread-safety should be handled in the CreateInstance() method. A simple solution is to call the StaticHelper.Get() method in the static constructors of classes defining a [StaticClass] attribute.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: