views:

110

answers:

4

I've caused myself some headaches over the past couple of weeks with the curiously recurring template pattern.

Following on from these two questions of mine:

How can I improve the following example:

public class DocketType : Enumeration<DocketType, int, string>
{
    public static DocketType Withdrawal = new DocketType(2, "Withdrawal");
    public static DocketType Installation = new DocketType(3, "Installation");

    private DocketType(int docketTypeId, string description) 
        : base(docketTypeId, description) { }
}

I want a static method that I don't have to repeat in the Enumeration class:

public abstract class Enumeration<TEnum, X, Y> : IComparable 
    where TEnum : Enumeration<TEnum, X, Y> 
{        
    protected Enumeration(X value, Y displayName)
    {
        AddToStaticCache(this);
    }
    public static TEnum Resolve(X value)
    {
        return Cache[value] as TEnum;
    }
}

The problem with this, as you'll see from my second linked question, is that the call to Enumeration<DocketType, int, string>.Resolve(X value); does not cause the DocketType static objects to be instantiated.

I'm not adverse to totally rewriting this from scratch. I'm aware it's a big code smell. Currently, to get this working my base class has the protected static method ChildResolve and I've added Resolve to each of my Enumeration classes. Nasty stuff!

ANSWER:

Seems there was no nice alternative to the pattern, so I stuck with the pattern and took inspiration from the accepted answer and came up with this:

static Enumeration()
{
    GetAll();
}

public static void GetAll()
{
    var type = typeof(TEnum);
    var fields = type.GetFields(BindingFlags.Public | 
        BindingFlags.Static | BindingFlags.DeclaredOnly);

    foreach (var info in fields)
    {
        var locatedValue = info.GetValue(null) as Enumeration<TEnum, X, Y>;
        Cache.Add(locatedValue.Value, locatedValue);
    }
}

This is also the same code using in the CodeCampServer MVC example project, so I feel less dirty for using it!

A: 

You want to do something "to all subclasses of a given type". Anything of that nature is impossible without using AppDomain.Current.GetAssemblies() and iterating through them. If you take this approach, you can optimize the performance by creating an assembly-level attribute that you only apply to your assemblies, (and others that should be included in your subclass search.) and use that when preparing to invoke .GetTypes() on each assembly.

To be clear, here's an example of what getting all those subclasses might look like:

Type[] subclasses = AppDomain.CurrentDomain.GetAssemblies()
    .Where(x => Attribute.IsDefined(typeof(MyEnumeartionAssemblyAttribute)))
    .SelectMany(x => x.GetTypes())
    .Where(x => x.BaseType != null && 
           x.BaseType.IsGenericType && 
           x.BaseType.GetGenericTypeDefinition() == typeof(Enumeration<,,>));

From there it should be a simple matter of using reflection on each System.Type and doing what you will with the static fields.

Kirk Woll
+2  A: 

You need to push your static fields into a class that has a static instance as instanced fields. That way you access your enumeration through a single static member, which immediately instances all the enumeration members.

A quickly thrown together example:

// The Collection of values to be enumerated
public class DocketEnum : EnumarationCollection<DocketType, int, string>
{
        // Values are fields on a statically instanced version of this class
    public DocketType Withdrawal = new DocketType(2, "Withdrawal");
    public DocketType Installation = new DocketType(3, "Installation");

    // The publicly accessible static enumeration 
    public static DocketEnum Values = new DocketEnum();
}

// The actual value class
public class DocketType : EnumerationValue<DocketType, int, string>
{
        // Call through to the helper base constructor
    public DocketType(int docketTypeId, string description) 
        : base(docketTypeId, description) { }
}

// Base class for the enumeration
public abstract class EnumarationCollection<TType, X, Y>
    where TType : EnumerationValue<TType, X, Y> 
{
            // Resolve looks at the static Dictionary in the base helpers class
    public TType Resolve(X value)
    {
        return Cache[value] as TType;
    }

    public static Dictionary<X, EnumerationValue<TType, X, Y> > Cache = new Dictionary<X, EnumerationValue<TType, X, Y>>();
}

// Base class for the value
public abstract class EnumerationValue<TType, X, Y> 
    where TType : EnumerationValue<TType, X, Y> 
{        
        // helper constructer talks directly the the base helper class for the Enumeration
    protected EnumerationValue(X value, Y displayName)
    {
        EnumarationCollection<TType, X,Y >.Cache.Add(value, this as TType);
    }
}



class MainClass
{
    public static void Main (string[] args)
    {
                    // You can immediately resolve to the enumeration
        Console.WriteLine(DocketEnum.Values.Resolve(2).ToString());
    }
}
Nicholas M T Elliott
+1 for the really good idea, but I'd rather not have the additional instance of `Values`. I had already tried this with DocketType.Instance.Changeover, but it just doesn't look very nice. I.e. I wanted it to just appear indistinguishable from a regular enum, but with the ability to have some advanced values with factory methods.
GenericTypeTea
+2  A: 

It's not very elegant, but something like this might do the trick:

public class DocketType : Enumeration<DocketType, int, string>
{
    public static readonly DocketType Withdrawal =
        new DocketType(2, "Withdrawal");

    public static readonly DocketType Installation =
        new DocketType(3, "Installation");

    private DocketType(int docketTypeId, string description)
        : base(docketTypeId, description) { }
}

public abstract class Enumeration<TEnum, TId, TDescription> : IComparable
    where TEnum : Enumeration<TEnum, TId, TDescription>
{
    private static readonly Dictionary<TId, TEnum> _cache;

    static Enumeration()
    {
        Type t = typeof(TEnum);
        _cache = t.GetFields(BindingFlags.Public | BindingFlags.Static)
                  .Where(f => f.FieldType == t)
                  .Select(f => (TEnum)f.GetValue(null))
                  .ToDictionary(e => e.Id, e => e);
    }

    public static TEnum Resolve(TId id)
    {
        return _cache[id];
    }

    public TId Id { get; private set; }
    public TDescription Description { get; private set; }

    protected Enumeration(TId id, TDescription description)
    {
        Id = id;
        Description = description;
    }

    // IComparable
    public int CompareTo(object obj)
    {
        // TODO
        throw new NotImplementedException();
    }
}
LukeH
@LukeH - Thanks! +1 and accepting as it inspired the answer I came up with.
GenericTypeTea
A: 

If you do want to force another class's static constructor to run, you can use RuntimeHelpers.RunClassConstructor. You can call it from the static constructor of Enumeration<TEnum, X, Y> so that it will be run the first time you use a static method on any instantiation of the generic type:

static Enumeration()
{
    RuntimeHelpers.RunClassConstructor(typeof(TEnum).TypeHandle);
}
Quartermeister