tags:

views:

68

answers:

1

Edit: I filed a bug report on microsoft connect:: https://connect.microsoft.com/VisualStudio/feedback/details/614234/delegate-createdelegate-allows-binding-functions-with-enum-parameters-to-the-enum-base-type#details

Consider the following thing:

public static Int32 Test(Int16 @short)
{
    return @short;
}

and from calling code::

Func<Int16,Int32> test = Test; //valid method group conversion.
Func<Int16,StringComparison> test2 = Test; // doesn't compile, not valid method group conversion.
Func<Int16,StringComparison> test3 = test; // doesn't compile, invalid contra-variance.
Func<Int16,StringComparison> test4 = Delegate.CreateDelegate(typeof(Func<Int16,StringComparison>),test.Method) as Func<Int16,StringComparison>; // works fine.

Why can Delegate.CreateDelegate do this strange conversion that no other means of creating a function allows? Even worse, a somewhat more sensible conversion to say Int64 or Object both fail. I realize that StringComparison "extends" Int32, But I thought that was more of a compiler trick as enum's extend the Enum class.

Also, this conversion works for DynamicMethod.CreateDelegate, as well.

Edit just tried it with Nullable<Int32> it doesn't work, which is perplexing. I think the only "free" conversion is to Enum types to their underlying type, but why?

Note that this does not allow you to convert an int instance method (like GetHashCode) to an open method that takes the enum type, which is why I think this is a bug as the behavior is inconsistent.

Edit: If we remove test2 and test3 line we can then test to see that the method,delegate, and the "illegal" delegate all work as expected.

Console.WriteLine(Test(0)); // prints 0
Console.WriteLine(test(0)); // prints 0
Console.WriteLine(test4(0)); //prints CurrentCulture

Edit: Here is a very big abuse of this that I wrote in about 10 minutes. This creates an IEqualityComparer<T> for an TEnum, by basically grabbing the one for its an underlying type and then just wrapping the Equals, and HashCode and using this trick/abuse to convert the parameters to TEnums, rather than underlying type. If this is a bug I'd like to know so that I won't try to rely on this behavior.

class EnumComparer<TEnum> : EqualityComparer<TEnum> where TEnum : struct
{
    static Func<TEnum, TEnum, bool> s_Equals;
    static Func<TEnum, int> s_HashCode;
    static EnumComparer<TEnum> s_default;
    static EnumComparer()
    {
        if (!typeof(TEnum).IsEnum) throw new Exception("Not an enum type");
        Type underlyingType = Enum.GetUnderlyingType(typeof(TEnum));
        object equalityComparer = typeof(EqualityComparer<>).MakeGenericType(new[] { underlyingType }).GetProperty("Default").GetGetMethod().Invoke(null, null);
        s_Equals = Delegate.CreateDelegate(typeof(Func<TEnum, TEnum, bool>), equalityComparer,equalityComparer.GetType().GetMethod("Equals", new[]{underlyingType,underlyingType})) as Func<TEnum,TEnum,bool>;
        s_HashCode = Delegate.CreateDelegate(typeof(Func<TEnum, int>), equalityComparer, equalityComparer.GetType().GetMethod("GetHashCode", new[]{underlyingType})) as Func<TEnum, int>;
        s_default = new EnumComparer<TEnum>();
    }

    public static  new EnumComparer<TEnum> Default
    {
        get
        {
            return s_default;
        }
    }
    public override bool Equals(TEnum x, TEnum y)
    {
        return s_Equals(x, y);
    }

    public override int GetHashCode(TEnum obj)
    {
        return s_HashCode(obj);
    }

    private EnumComparer()
    {
    }
}
+1  A: 

This is not a bug. It is always possible to convert an integral value type to an enum like StringComparison. The compiler normally requires a cast but you are bypassing the compiler here. And just as in C#, there is no check to verify that the integral value actually represents one of the enumeration values.

Hans Passant
Are you sure? When I inspect `test4.Method` it says its "Int32 Test(Int16)", which doesn't make sense, as it is completely wrong. I've written a lot of code with expression and I must always cast `Int32->enum` type for the return or else the `Expression<TDelegate>.Compile()` will throw an exception saying invalid return type.
Michael B
It makes lots of sense, that's the delegate definition for *test*, `Func<Int16, Int32>'. The Expression class has its own plumbing to check conversions, more restrictive than what the CLR allows.
Hans Passant