Introduction
An enum
in C# is a value type used to represent a set of fixed distinct values of integer type such as byte
, sbyte
, short
, ushort
, int
, uint
, long
or ulong
. An article suggests the helper class set to create enum
- like classes that can contain objects of C# classes.
Background
There are some articles on this subject, for example - here. This article proposes a bit different and a bit more "safe" way of creating and using enhanced enumerations.
The Main Point
The key point of the solution is: the constructor of the class of "enum
" value should be the private
one. In this case, we cannot create the object of the class with the help of the "new
" operator. The object can be created either with the help of C# reflection, or with the help of the helper class method (the helper class uses the reflection also).
Using the Code
The class EnumValueHelper
has one essential method - CreateObject
, which creates the object of our underlying "enum
data" class with a private
constructor and initializes its public
variables using C# reflection. Please note, what the order of the parameters at CreateObject
method call should be the same as the order of the parameter declaration in the underlying "enum
value" class (EnumLikeClassValue
in this sample) below.
public abstract class EnumValueHelper<T> where T : class
{
protected static T CreateObject(params object[] args)
{
var type = typeof (T);
Type[] paramTypes = {};
var constructorInfo = type.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic,
null, paramTypes, null);
var obj = (T) constructorInfo.Invoke(new object[] {});
var fieldInfoArray = type.GetFields(BindingFlags.Instance | BindingFlags.Public);
if (fieldInfoArray.Length != args.Length)
{
throw new ArgumentException((typeof (EnumValueHelper<T>)).FullName +
" CreateObject: wrong input paramerter count");
}
for (var i = 0; i < args.Length; i++)
{
try
{
if (args[i].GetType() != fieldInfoArray[i].FieldType)
{
throw new ArgumentException((typeof (EnumValueHelper<T>)).FullName +
" CreateObject: wrong input paramerter type for position " + i);
}
fieldInfoArray[i].SetValue(obj, args[i]);
}
catch (Exception ex)
{
throw new Exception((typeof (EnumValueHelper<T>)).FullName +
" CreateObject: unknown failure for position " + i + " message = " + ex.Message);
}
}
return obj;
}
public override string ToString()
{
var fiaBase = GetType().GetFields(BindingFlags.Instance | BindingFlags.Public);
var sb = new StringBuilder();
for (int i = 0; i < fiaBase.Count(); i++)
{
sb.Append(fiaBase[i].GetValue(this));
sb.Append(";");
}
return sb.ToString();
}
}
The class EnumClassHelper
has one method - ToList
, which returns the list of underlying "enum
elements" in our enum
- like type.
public abstract class EnumClassHelper<T1, T2> : EnumValueHelper<T2>
where T1 : class
where T2 : class
{
public static List<T2> ToList()
{
var fields = (typeof(T1)).GetFields(BindingFlags.Static | BindingFlags.Public);
return fields.Select(x => (T2)x.GetValue(null)).ToList();
}
}
Now we can create underlying "enum
value" class of our "enum
" type. Note, what the class has private
constructor. It makes impossible the class object creation with the help of the C# new
operator. To use this class entirely correctly, we should override some methods in the underlying enum
value class of our suggested enum
type:
public class EnumLikeClassValue : EnumValueHelper<EnumLikeClassValue>
{
public readonly string StringValue;
public readonly int IntValue;
public readonly double DoubleValue;
private EnumLikeClassValue()
{
}
public override bool Equals(object obj)
{
var x = obj as EnumLikeClassValue;
if (x == null) return false;
return StringValue == x.StringValue && IntValue == x.IntValue && DoubleValue.Equals(x.DoubleValue);
}
public static bool operator ==(EnumLikeClassValue a, EnumLikeClassValue b)
{
if (System.Object.ReferenceEquals(a, b))
{
return true;
}
if (((object)a == null) || ((object)b == null))
{
return false;
}
return a.Equals(b);
}
public static bool operator !=(EnumLikeClassValue a, EnumLikeClassValue b)
{
return !(a == b);
}
public override int GetHashCode()
{
return new {StringValue, IntValue, DoubleValue}.GetHashCode();
}
}
As we can see, the methods Equals
, and GetHashCode
operators ==
and !=
in the underlying "enum
" class have been overriden. Please remember to have the constructor in this class private
After that, we can complete our "enum
" example, i.e., create our "enum
" class. Please note, the members of this class should be static
, so these members can be used by the ToList()
function of EnumClassHelper
:
public class ClassLikeEnumTest : EnumClassHelper<ClassLikeEnumTest, EnumLikeClassValue>
{
public static readonly EnumLikeClassValue Value1 = CreateObject("StringValue1", 1, 1.0);
public static readonly EnumLikeClassValue Value2 = CreateObject("StringValue2", 2, 2.0);
public static readonly EnumLikeClassValue Value3 = CreateObject("StringValue3", 3, 3.0);
}
Note: As the EnumLikeClassValue
class has private
constructor, we cannot create EnumLikeClassValue
object with new
operator. We can initialize it using C# reflection - or, in our case, with the help of our ClassLikeEnumTest
values: ClassLikeEnumTest.Value1, ClassLikeEnumTest.Value2, ClassLikeEnumTest.Value3
.
Well, how to use it? Please look at the below sample:
class Program
{
static void Main(string[] args)
{
EnumLikeClassValue value = ClassLikeEnumTest.Value1;
Console.WriteLine(value);
foreach (var x in ClassLikeEnumTest.ToList())
{
Console.WriteLine();
Console.WriteLine(x);
Console.WriteLine();
}
foreach (var x in ClassLikeEnumTest.ToList())
{
Console.WriteLine();
Console.WriteLine(x == value);
Console.WriteLine();
}
foreach (var x in ClassLikeEnumTest.ToList())
{
Console.WriteLine();
Console.WriteLine(x.Equals(value));
Console.WriteLine();
}
}
}
The output for this program is:
StringValue1;1;1;
StringValue1;1;1;
StringValue2;2;2;
StringValue3;3;3;
True
False
False
True
False
False
Press any key to continue . . .
As one can see, the source code is quite short and straightforward: it can be simply copy pasted in your project. Thanks!