Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

One More C# Enum Enhancement

0.00/5 (No votes)
21 Jun 2014 1  
The tip describes a way to create C# enhanced enumerations that can contain class objects.

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 = {}; // T has parametrless ctor

            var constructorInfo = type.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic,
                null, paramTypes, null);

            var obj = (T) constructorInfo.Invoke(new object[] {}); // T has parametrless ctor

            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>
    {
        // Enum class data. For example one int, one double and one string
        // Only instance members!!!
        public readonly string StringValue;
        public readonly int IntValue;
        public readonly double DoubleValue;

        // parametrless ctor 
        private EnumLikeClassValue()
        {
        }

        // overrides to make the stuff like operator(s) ==; etc... work correctly
        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 both are null, or both are same instance, return true.
            if (System.Object.ReferenceEquals(a, b))
            {
                return true;
            }

            // If one is null, but not both, return false.
            if (((object)a == null) || ((object)b == null))
            {
                return false;
            }

            // Return true if the fields match:
            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!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here