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.