2010-08-10 9 views
3

Je me suis causé quelques maux de tête au cours des deux dernières semaines avec le modèle curiously recurring template.Existe-t-il une alternative au modèle de modèle curieusement récurrent?

Faisant suite à ces deux questions à moi:

Comment puis-je améliorer l'exemple suivant:

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) { } 
} 

Je veux une méthode statique que je n'ai pas à répéter dans le Enumeration classe:

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; 
    } 
} 

Le problème avec cela, comme vous le verrez de ma deuxième question liée, est que l'appel à Enumeration<DocketType, int, string>.Resolve(X value); ne provoque pas les objets statiques DocketType à instancier.

Je ne suis pas opposé à totalement réécrire cela à partir de zéro. Je sais que c'est une grosse odeur de code. Actuellement, pour que cela fonctionne, ma classe de base a la méthode statique protégée ChildResolve et j'ai ajouté Resolve à chacune de mes classes Enumeration. Trucs méchants!

RÉPONSE:

semble qu'il n'y avait pas d'alternative agréable au motif, donc je coincé avec le motif et se est inspiré de la réponse acceptée et est venu avec ceci:

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); 
    } 
} 

C'est aussi le même code en utilisant dans le projet exemple CodeCampServer MVC, donc je me sens moins sale pour l'utiliser!

Répondre

3

Ce n'est pas très élégant, mais quelque chose comme ça pourrait faire l'affaire:

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(); 
    } 
} 
+0

Merci! +1 et en acceptant comme il a inspiré la réponse que j'ai trouvée. – GenericTypeTea

0

Vous voulez faire quelque chose "à toutes les sous-classes d'un type donné". Tout ce qui est de cette nature est impossible sans utiliser AppDomain.Current.GetAssemblies() et itérer à travers eux. Si vous adoptez cette approche, vous pouvez optimiser les performances en créant un attribut au niveau de l'assembly que vous appliquez uniquement à vos assemblys (et à d'autres qui doivent être inclus dans votre recherche de sous-classe) et utilisez-le lors de la préparation de l'appel .GetTypes() sur chaque assemblage.

Pour être clair, voici un exemple de ce que tous ces sous-classes se pourrait ressembler à:

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<,,>)); 

De là, il devrait être une simple question d'utiliser la réflexion sur chaque System.Type et de faire ce que vous voudrez les champs statiques.

2

Vous devez placer vos champs statiques dans une classe qui a une instance statique en tant que champs instanciés. De cette façon, vous accédez à votre énumération par le biais d'un seul membre statique, qui installe immédiatement tous les membres de l'énumération.

Un rapidement jetés ensemble par exemple:

// 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()); 
    } 
} 
+0

+1 pour la très bonne idée, mais je préférez ne pas avoir l'instance supplémentaire de 'Values'. J'avais déjà essayé ceci avec DocketType.Instance.Changeover, mais cela n'a pas l'air très joli. C'est à dire. Je voulais que cela apparaisse indiscernable d'une énumération régulière, mais avec la possibilité d'avoir des valeurs avancées avec des méthodes d'usine. – GenericTypeTea

0

Si vous ne voulez forcer constructeur statique d'une autre classe pour exécuter , vous pouvez utiliser RuntimeHelpers.RunClassConstructor. Vous pouvez l'appeler du constructeur statique de Enumeration<TEnum, X, Y> de sorte qu'il sera exécuté la première fois que vous utilisez une méthode statique sur une instanciation du type générique:

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