I searched around and couldn't find any examples doing this, though this was helpful:
Create Generic method constraining T to an Enum
I have a generic function which wraps a function in an API (which I can't touch). The wrapped function takes a System.Enum and returns the same. My generic version simplifies the things quite a bit in the non-stripped down version of this example.
The problem is, I couldn't case from T to System.Enum, or back again, since T isn't constrained to System.Enum (at least that is my understanding).
The following code works but I am curious to know if there are any hidden traps, or better ways, since I am very new to generics:
using System
using System.Collections.Generic
...
public T EnumWrapper<T>(T enumVar) where T : struct, IFormattable, IConvertible, IComparable
{
if (!typeof(T).IsEnum)
throw new ArgumentException("Generic Type must be a System.Enum")
// Use string parsing to get to an Enum and back out again
Enum e = (Enum)Enum.Parse(typeof(T), enumVar.ToString());
e = WrappedFunction(e);
return (T)Enum.Parse(typeof(T), e.ToString());
}
If this is ok, then let this serve as an example. I couldn't find this and at the very least it is a working work-around.
P.S. Performance isn't an issue in this case. thought I was thinking string-work might be slow and I am always interested in performance tips.
You can make the constraint tighter. All enums implement the followable interfaces IFormattable
,IConvertible
and IComparable
. This will pretty much limit you to primitives and enums and classes which act like them.
Alternatively, if you really want to create a method or class only bindable to enums you can do the following:
public abstract class InvokerHelper<T> where T : class
{
public abstract R Invoke<R>(R value) where R : struct,T;
}
public class EnumInvoker : InvokerHelper<Enum>
{
public override R Invoke<R>(R value)
{
return (R)WrappedMethod(value);
}
}
Its clunky and can't be used for extension methods, but you could make this a singleton object and then only have generic methods.
Alternatively, you can write your code in C++/CLI
or Reflection.Emit
which allows you to create classes that can have the constraint where T:struct,Enum
Actually it appears you just want to take a generic T call a method that takes an enum and return it as T right?
Then the following will work.
public static T WrappedMethodInvoker<T>(T value) where T:struct,IComparable,IFormattable,IConvertible
{
Enum e;
if((e = value as Enum) == null)
throw new ArgumentException("value must be an Enum")
object res = WrappedMethod(e);
return (T)res;
}
This is a little easier. First we know T is an Enum
, you can use the as
operator to try casting to a nullable type (reference type or nullable struct), which Enum
most certainly is. From there we can use covariance of return types, WrappedMethod
returns an Enum
, which means we can store it in an object. Finally, when you have an object in a generic method you are syntactically allowed to cast it to T. It might fail, but we know for a fact the method returns an Enum of the same type.
There are some costs to know in that you are always boxing and unboxing. Is the wrapped method generic?
I've edited the answer to show the first technique revised to your example case. Its extremely simple and type safe, but you always have to have an EnumInvoker to perform the actions. Both answers should perform about the same unfortunately, as calling the WrappedMethod will box value if its argument is Enum. The only advantage of the first method is that it is strongly typed, but generic virtual methods are the slowest methods to invoke as far as I'm aware. So avoiding the type check might not be worth he cost of easier invocation.
The matter is simple here: C#'s Type System doesn't let you specify a constraint on an Enum type. The code has no obvious pitfalls, besides it won't trigger a compile time error if you pass it an IConvertible struct that is not an Enum. In this case it will fail at runtime, which is not desirable but there is no better option, really.
You might be able to enforce this with CodeContracts at compile time though, although I don't know enough about CodeContracts (though I fell in love with them) yet, to give you a definite answer here.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With