2011-09-26 1 views
3

Ma question concerne en fait un moyen de contourner l'initialisation de champs statiques par C#. J'ai besoin de faire cela, dans ma tentative de dupliquer un enum de style Java. Voici un exemple du code qui montre le problème:Initialisation de champs statiques en C# à utiliser dans un motif enum

Une classe de base pour tous mes énumérations à hériter de

public class EnumBase 
{ 
    private int _val; 
    private string _description; 

    protected static Dictionary<int, EnumBase> ValueMap = new Dictionary<int, EnumBase>(); 

    public EnumBase(int v, string desc) 
    { 
     _description = desc; 
     _val = v; 
     ValueMap.Add(_val, this); 
    } 

    public static EnumBase ValueOf(int i) 
    { 
     return ValueMap[i]; 
    } 

    public static IEnumerable<EnumBase> Values { get { return ValueMap.Values; } } 

    public override string ToString() 
    { 
     return string.Format("MyEnum({0})", _val); 
    } 
} 

Un échantillon d'un ensemble énuméré:

public sealed class Colors : EnumBase 
{ 
    public static readonly Colors Red = new Colors(0, "Red"); 
    public static readonly Colors Green = new Colors(1, "Green"); 
    public static readonly Colors Blue = new Colors(2, "Blue"); 
    public static readonly Colors Yellow = new Colors(3, "Yellow"); 

    public Colors(int v, string d) : base(v,d) {} 
} 

C'est où le problème est:

class Program 
{ 
    static void Main(string[] args) 
    { 
     Console.WriteLine("color value of 1 is " + Colors.ValueOf(2)); //fails here 
    } 
} 

Le code ci-dessus échoue car EnumBase.ValueMap contient zéro élément , car aucun des constructeurs de Color n'a encore été appelé.

Il semble que cela ne devrait pas être si difficile à faire, c'est possible en Java, je sens qu'il me manque quelque chose ici?

Répondre

1

Il vous manque le point d'un membre statique. Un membre statique est membre du type, pas l'instance. Lorsque vous appelez Colors.ValueOf() vous accédez uniquement au type, une instance n'a pas été créée de ce type - les constructeurs d'instance ne seront pas appelés du tout.

Vous pouvez créer une méthode d'extension pour l'énumération Color si vous souhaitez simplement pouvoir définir certains comportements. Vous pouvez obtenir le ENUM d'une valeur simplement par coulée à partir de sa base int:

public enum Color { Red = 0, Green = 1, Blue = 2, Yellow = 3 } 

public static class ColorExtensions 
{ 
    public static string GetString(this Color color) 
    { 
     return string.Format("MyEnum({0})", color); 
    } 
} 

Console.WriteLine("color value of 1 is " + ((Color)1).GetString()); 

Vous trouverez également un certain nombre de méthodes utiles dans la classe System.Enum (http://msdn.microsoft.com/en- us/library/system.enum.aspx). Il existe des méthodes pour analyser à partir de string ou pour obtenir une collection de toutes les valeurs enum possibles.

+0

mais ne devrait pas le constructeur statique pour les couleurs, qui initialiser toutes les variables membres statiques (qui sont, à leur tour, les couleurs) ont été appelés? – SirPentor

+0

Oui, désolé je l'ai mieux qualifié maintenant. Seuls les constructeurs d'instance ne seront pas appelés. –

+0

Je ne comprends toujours pas pourquoi les constructeurs d'instance ne seront pas appelés. Ils sont instanciés dans le constructeur statique, qui s'appelle, n'est-ce pas? Qu'est-ce que je ne reçois pas ici? – SirPentor

6

Ce motif ne va pas fonctionner. Avoir un seul dictionnaire ne va pas être une bonne idée, que ce soit - Je suppose que vous voulez faire de votre EnumBase abstraite et générique:

public abstract class EnumBase<T> where T : EnumBase<T> 

Cela pourrait alors un membre statique protégé qui peut être efficace " publié » dans chaque classe dérivée:

public abstract class EnumBase<T> where T : EnumBase<T> 
{ 
    protected static T ValueOfImpl(int value) 
    { 
     ... 
    } 
} 

public class Color : EnumBase<Color> 
{ 
    // static fields 

    // Force initialization on any access, not just on field access 
    static Color() {} 

    // Each derived class would have this. 
    public static Color ValueOf(int value) 
    { 
     return ValueOfImpl(value); 
    } 
} 

Cela vous oblige à accéder à la classe elle-même Color ... à quel point les champs seront initialisées, en raison de la initialiseur statique.

Il y a bien quelques petites choses à faire pour faire tout ce travail, malheureusement :(

+0

Ma mise en œuvre actuelle l'a comme abstrait, et générique, et les couleurs comme scellé. Mais oui, après avoir battu ma tête contre le mur, il me semble que je suis coincé avec 'enum', des attributs personnalisés, et une classe EnumExtension basée sur un modèle qui utilise la réflexion.Ce sera un code propre :) – Sheamus

2

Je crois que ce que vous essayiez donc dire dans le code est tout simplement:

public enum Colors 
    { 
     [Description("Red")] 
     Red = 0, 
     [Description("Green")] 
     Green = 1, 
     [Description("Blue")] 
     Blue = 2 
     //etc... 
    } 

Vous pouvez facilement lire l'attribut Description en utilisant la réflexion.Vous pouvez même créer des méthodes d'extension pour les énumérations de couleurs si vous voulez et implémentez quelque chose de similaire à ValueOf comme méthode d'extension.

+0

Oui dans mon exemple de cas minime, votre réponse fonctionne. – Sheamus

+1

Mais les enums C# sont très limités par rapport à Java alors j'espérais trouver quelque chose de similaire à Java. Je ne suis pas sûr de savoir comment implémenter ValueOf (int x) en tant que méthode d'extension. Bien que ce ne serait pas nécessaire de toute façon, comme int peut être le cas à enums. Mais encore, c'est une limitation des extensions; vous ne pouvez pas avoir de méthodes de classe. c'est-à-dire MyEnum.DoSomthing (5). Vous pouvez faire cette instance MyEnum; instance.DoSomething (5), mais c'est différent. De toute façon, je me rends compte qu'il me semble que je vais devoir utiliser de vieux enums, avec une classe d'extension modélisée. – Sheamus

+0

Je ne suis pas sûr de ce que vous essayez d'accomplir avec MyEnum.DoSomething. Les énumérations (en C++ et C#) sont juste une liste structurée de nombres, pas un ensemble de données et d'opérations (comme une classe). – SirPentor

0

Cela peut être fait, et c'est en fait très utile. Vous obtenez le type de sécurité lors de la définition des variables et la possibilité de rechercher dynamiquement. Je préférerais voir moins de code dans la classe enfant, mais néanmoins, ça marche bien. Vous pouvez également développer et augmenter le nombre de champs dans EnumBase et également remplacer les opérateurs et les méthodes ToString, etc. Vous pouvez également ajouter plus de génériques dans le mélange. C'est Enums sur les stéroïdes.

Modifié: Lire en bas d'entrée

EnumBase:

public class EnumBase 
{ 
    public int Val { get; private set; } 
    public string Description { get; private set; } 

    private static readonly Dictionary<int, EnumBase> ValueMap = new Dictionary<int, EnumBase>(); 

    protected EnumBase(int v, string desc) 
    { 
     Description = desc; 
     Val = v; 
    } 

    protected static void BuildDictionary<T>() 
    { 

     var fields = typeof(T).GetFields(BindingFlags.Public | BindingFlags.Static); 
     foreach (var field in fields) 
     { 
      ValueMap.Add(((EnumBase)field.GetValue(null)).Val, (EnumBase)field.GetValue(null)); 
     } 
    } 

    public static EnumBase ValueOf(int i) 
    { 
     return ValueMap[i]; 
    } 
} 

Couleurs:

public sealed class Colors : EnumBase 
{ 
    public static readonly Colors Red = new Colors(0, "Red"); 
    public static readonly Colors Green = new Colors(1, "Green"); 
    public static readonly Colors Blue = new Colors(2, "Blue"); 
    public static readonly Colors Yellow = new Colors(3, "Yellow"); 

    public Colors(int v, string d) : base(v, d) 
    { 
    } 

    static Colors() 
    { 
     BuildDictionary<Colors>(); 
    } 
} 

Utilisation:

//example of type safety 
var i = Colors.Blue.Val; 

//example of dynamic search 
Console.WriteLine(Colors.ValueOf(1).Description); 

Modifié:

Le code ci-dessus ne fonctionne pas si vous héritez d'EnumBase plus d'une fois (ce qui constitue un énorme problème). Les méthodes statiques ne sont pas héritées, c'est-à-dire que toutes les classes enfants ajouteront simplement plus d'enregistrements au dictionnaire de classes de base statique.

Si le cas d'utilisation est assez forte, vous pouvez réutiliser le code au lieu d'essayer d'utiliser l'héritage:

public sealed class Colors 
{ 
    public int Val { get; private set; } 
    public string Description { get; private set; } 

    private static readonly Dictionary<int, Colors> ValueMap = new Dictionary<int, Colors>(); 

    static Colors() 
    { 
     BuildDictionary<Colors>(); 
    } 

    private static void BuildDictionary<T>() 
    { 
     var fields = typeof(T).GetFields(BindingFlags.Public | BindingFlags.Static); 
     foreach (var field in fields) 
     { 
      ValueMap.Add(((Colors)field.GetValue(null)).Val, (Colors)field.GetValue(null)); 
     } 
    } 

    public static Colors ValueOf(int i) 
    { 
     return ValueMap[i]; 
    } 

    private Colors(int v, string desc) 
    { 
     Description = desc; 
     Val = v; 
    } 

    public static readonly Colors Red = new Colors(0, "Red"); 
    public static readonly Colors Green = new Colors(1, "Green"); 
    public static readonly Colors Blue = new Colors(2, "Blue"); 
    public static readonly Colors Yellow = new Colors(3, "Yellow"); 

} 
Questions connexes