1

Description

I have a function named HandleEnum which accept an enum as parameter e, and I need convert e from enum to int and do some operation based on its int value. Finally, I need to convert it back to enum and return it. (The requirement might looks a little strange, because it's just a abstract of my real problem).

Method 1: generic

I have tried to use generic, and it does help:

static object HandleEnumViaGeneric<T>(T e)
{
    int x = (int)(object)e + 1;
    return (T)(object) x;
}

It can be called like below:

enum Color { black, red, green };
enum Day { day1, day2, day3, day4 };

static void Main(string[] args)
 {
    Console.WriteLine(HandleEnumViaGeneric(Day.day3).GetType());
    Console.WriteLine(HandleEnumViaGeneric(Color.black).GetType());
 }

Method2: reflection

Meanwhile, I tried to use reflection to do the same thing, but it failed:

static object HandleEnumViaReflection(object e)
{
    int x = (int) e + 1;
    return Activator.CreateInstance(e.GetType(), x);
}

When I called: Console.WriteLine(HandleEnumViaReflection(Color.black).GetType());, an exception has been throw: Constructor on type 'TestEnum.Color' not found. (TestEnum is my namespace).

My question

Here is my question:

  1. How can the second method work?
  2. To meet my requirement, which method is better or none of them are good method?

thx.

Caesium
  • 789
  • 3
  • 7
  • 24
  • I want to know why method2 doesn't work and actually which method is better. – Caesium Oct 18 '18 at 03:40
  • 2
    The first method will be faster, and it works. The second is slower and doesn't work. I'd suggest the first method is better. – mjwills Oct 18 '18 at 03:41
  • I suggest that something might be wrong in your design. There are much better (and safer) ways to find the next value in an enum. What happens with this instruction: `HandleEnumViaGeneric(Day.day4)` ? And with this one: `HandleEnumViaGeneric(new MyObject())` ? And with this one too: `enum Days { first = 0, second = 2 }; HandleEnumViaGeneric(Days.first)` ? You might consider [another implementation](https://stackoverflow.com/a/643438/4370629). – Spotted Oct 18 '18 at 04:58
  • @Spotted This is just a abstraction of the real problem. More complex things will be done when the enum is cast to int. – Caesium Oct 18 '18 at 12:16
  • @Caesium I understand that the example is simplified, however the underlying problem is still there: casting an enum to an int which is a brittle operation. – Spotted Oct 18 '18 at 12:32

4 Answers4

0

Method 2 doesn't work because you are using CreateInstance(Type, Object[]) where the parameters must match a constructor and your object is an enum. I would suggest going for your 1st approach as I don't think it's possible to implement the 2nd one.

// Summary:
//     Creates an instance of the specified type using the constructor that best
//     matches the specified parameters.

In short, Activator.CreateInstance only works for objects with constructors.

jegtugado
  • 5,081
  • 1
  • 12
  • 35
  • Yes, I also don't think reflection will work,but I found that using Enum.ToObject(e.GetType(), x) will avoid using generic. – Caesium Oct 18 '18 at 04:13
0

regarding first question , try this one :

static T HandleEnumViaGeneric<T>(int id)
{
    if(!typeof(T).IsEnum)
        throw new Exception("");

    var values = Enum.GetValues(typeof(T));  
    return (T)values.GetValue(id);
}

Regarding second question, suppose to throw exception , because you can't implement parametric constructor for enum

Z.R.T.
  • 1,543
  • 12
  • 15
0
static object HandleEnumViaReflection(object e)
{
    int x = (int) e + 1;
    return Activator.CreateInstance(e.GetType(), x);
}

This does not work--but it could. In fact we can create the type if it can't be found at runtime.

If you must use reflection or other indirect means then you could define your own enumeration with a ModuleBuilder instance and an IEnumerable<KeyValuePair<string,int>>. Below we are passing in the IEnumerable only because it simplifies the method body.

static object HandleEnumViaOtherMeans(object e, List<KeyValuePair<string,int>> colors)
{
    int x = (int) e + 1;
    var arbitraryName = new AssemblyName(Guid.NewGuid().Replace("-", string.Empty));
    var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(arbitraryName, AssemblyBuilderAccess.Run);
    var builder = assemblyBuilder.DefineDynamicModule(arbitraryName.Name);

    var colorEnum = builder.DefineEnum("Color", TypeAttributes.Public, typeof(int));
    foreach(var color in colors)
    {
        colorEnum.DefineLiteral(color.Key,color.Value);
    }
    var complete = colorEnum.CreateType();
    return Enum.ToObject(complete, x);
}

This is not generally a good way to achieve your purpose but it can be useful when the color palette is not known in advance (i.e., at design or build time).

However most would extract the type generation into a separate static method in this case so that among other things we could avoid rebuilding the missing type.

public class ColorHandler
{
   static ColorHandler() 
   {
      CurrentColors = new List<KeyValuePair<string,int>>();
   }

   public static List<KeyValuePair<string,int>> CurrentColors { get; set; } 
   private static Type _colorType;
   public static Type ColorType => _colorType ?? (_colorType = DefineColor());

   private static DefineColor()
   {
       var arbitraryName = new AssemblyName(Guid.NewGuid().Replace("-", string.Empty));
       var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(arbitraryName, AssemblyBuilderAccess.Run);
       var builder = assemblyBuilder.DefineDynamicModule(arbitraryName.Name);

       var colorEnum = builder.DefineEnum("Color", TypeAttributes.Public, typeof(int));
       foreach(var color in CurrentColors)
       {
           colorEnum.DefineLiteral(color.Key,color.Value);
       }
       return colorEnum.CreateType();
   }

   public static object HandleEnum(object e)
   {
      int x = (int) e + 1;
      return Enum.ToObject(ColorType, x);
   }
}

Now we are almost full circle. Especially if we have a generic class and still want to use Activator.CreateInstance() in a non-trivial way.

public class ColorConsumer<C> where C : struct
{
    public C InstanceColor { get; set; }
    public ColorConsumer(dynamic color)
    {
       InstanceColor = color;
    }

    //we can move the HandleEnum method here from ColorHandler
   public object HandleEnum()
   {
      int x = (int) InstanceColor + 1;
      return Enum.ToObject(typeof(C), x);
   }
}

Now we can update the ColorHandler class.

public class ColorHandler
{
   static ColorHandler() 
   {
      CurrentColors = new List<KeyValuePair<string,int>>();
   }
   private static List<KeyValuePair<string,int>> _current;
   public static List<KeyValuePair<string,int>> CurrentColors 
   { 
     get
     {
        return _current;
     }
     set
     {
         if(value != _current && (null != _colorType))
         {
            _current = Value;
            _colorType = DefineColor();
         }
     }
   } 
   private static Type _colorType;
   public static Type ColorType => _colorType ?? (_colorType = DefineColor());

   private static DefineColor()
   {
       var arbitraryName = new AssemblyName(Guid.NewGuid().Replace("-", string.Empty));
       var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(arbitraryName, AssemblyBuilderAccess.Run);
       var builder = assemblyBuilder.DefineDynamicModule(arbitraryName.Name);

       var colorEnum = builder.DefineEnum("Color", TypeAttributes.Public, typeof(int));
       foreach(var color in CurrentColors)
       {
           colorEnum.DefineLiteral(color.Key,color.Value);
       }
       return colorEnum.CreateType();
   }

   public static object HandleEnumViaReflection(object e)
   {
      var openType = typeof(ColorConsumer<>);
      var typeToActivate = openType.MakeGenericType(new Type[]{ ColorType });
      var consumer = Activator.CreateInstance(typeToActivate, new object[]{ e });
      return consumer.HandleEnum();
   }
}

Now we have both generic parameters and a call to Activator.CreateInstance().

QEF. Let's see what it looks like to call this.

var e = anInstanceOfSomeEnumeration;
ColorHandler.CurrentColors.Add(new KeyValuePair<string,int>("Red", 0));
ColorHandler.CurrentColors.Add(new KeyValuePair<string,int>("Blue", 1));
var f = ColorHandler.HandleEnumViaReflection(e);

If e is a sufficiently low value of any enumeration then f is set to Color.Blue on line 4. This enumeration is not defined on line 3.

Update

Actually HandleEnum() should be invoked in the constructor.

public ColorConsumer(dynamic color)
{
   InstanceColor = HandleEnum(color);
}
// ...
//then we update HandleEnum very slightly
public object HandleEnum(object e)
{
  int x = (int) e + 1;
  return Enum.ToObject(typeof(C), x);
}

Now HandleEnumViaReflection must be updated.

public static object HandleEnumViaReflection(object e)
{
   var openType = typeof(ColorConsumer<>);
   var typeToActivate = openType.MakeGenericType(new Type[]{ ColorType });
   var consumer = Activator.CreateInstance(typeToActivate, new object[]{ e });
   return consumer.InstanceColor;
}

All else is unchanged.

  • Actually reflection is optianal, I only want to try to find out a best solution for my requirement. – Caesium Oct 18 '18 at 04:31
0

you can do this:

static object HandleEnumViaGeneric<T>(T e)
{
    var genType = typeof(T);
    if(genType.IsEnum)
    {
       foreach (T object in Enum.GetValues(genType))
       {
           Enum temp =  Enum.Parse(typeof(T), e.ToString()) as Enum;
           return Convert.ToInt32(temp);
       }
     }
}

More here: How do I cast generic enum to int?

Gauravsa
  • 6,330
  • 2
  • 21
  • 30