Initializing static fields in C# for use in enum pattern
My question is actually about a way to work around how C# initializes static fields. I need to do this, in my attempt to duplicate a Java style enum. The following is an example of the code that shows the problem:
A base class for all my enums to inherit from
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 ToStrin开发者_开发知识库g()
{
return string.Format("MyEnum({0})", _val);
}
}
A sample of an enumerated set:
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) {}
}
This is where the problem is:
class Program
{
static void Main(string[] args)
{
Console.WriteLine("color value of 1 is " + Colors.ValueOf(2)); //fails here
}
}
The above code fails because EnumBase.ValueMap contains zero items, because none of the constructors for Color have been called yet.
It seems like this shouldn't be that hard to do, it is possible in Java, I feel like I must be missing something here?
That pattern basically isn't going to work. Having a single dictionary isn't going to be a good idea, either - I suspect you want to make your EnumBase
abstract and generic:
public abstract class EnumBase<T> where T : EnumBase<T>
That could then have a protected static member which can be effectively "published" through each derived class:
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);
}
}
This then forces you to access the Color
class itself... at which point the fields will be initialized, due to the static initializer.
There are quite a few things to be done to make all of this work, unfortunately :(
I believe what you were trying so say in code is simply:
public enum Colors
{
[Description("Red")]
Red = 0,
[Description("Green")]
Green = 1,
[Description("Blue")]
Blue = 2
//etc...
}
You can easily read the Description attribute using reflection.. You can even create an extention methods for the Colors enums if you want and implement something similar to ValueOf as an extention method
You're missing the point of a static member. A static member is a member of the type, not the instance.
When you call Colors.ValueOf()
you are only accessing the type, an instance has not been created of that type - the instance constructors won't be called at all.
You could create an extension method for the Color
enum if you just want to be able to define some behaviours. You could get the enum from a value simply by casting from its int
base:
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());
You'll also find a number of useful methods in the System.Enum
class (http://msdn.microsoft.com/en-us/library/system.enum.aspx). There are methods there to parse from string
or to get a collection of all the possible enum
values.
This can be done, and it is actually quite useful. You get type safety when setting variables and the ability to search dynamically. I'd prefer to see less code in the child class, but none the less, it works well. You could also expand upon this and grow the number of fields in the EnumBase and also override operators and the ToString methods, etc. You could also throw more generics into the mix. It's Enums on steroids.
Edited: Read bottom of entry
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];
}
}
Colors:
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>();
}
}
Usage:
//example of type safety
var i = Colors.Blue.Val;
//example of dynamic search
Console.WriteLine(Colors.ValueOf(1).Description);
Edited:
The above code doesn't work if you inherit from EnumBase more than once (which is a huge problem). The static methods aren't inherited, i.e. all child classes will just add more records to the static base class Dictionary.
If the use case is strong enough, you can reuse the code instead of trying to use inheritance:
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");
}
精彩评论