Piotr Zierhoffer Piotr Zierhoffer - 2 days ago 5
C# Question

Unexpected difference in DefaultValue and RawDefaultValue for Enum parameters

Consider the following example:

class Program
{
static void Main(string[] args)
{
foreach(var par in typeof(A).GetMethod("Method").GetParameters())
{
Console.WriteLine("Def {0}, RawDef {1}",
par.DefaultValue, par.RawDefaultValue);
}
}
}

class A
{
public void Method(int a = 5, B b = B.b){}
}
enum B
{
a = 0, b = 1
}


According to the documentation of
RawDefaultValue
and
DefaultValue
, with support from StackOverflow, these two methods of accessing the default value should return the same data.

But instead, I get the following output:

Def 5, RawDef 5
Def b, RawDef 1


So, apparently,
RawDefaultValue
drops the information about the parameter being an enum type.

My question: is it a bug or is it justified by another part of the documentation?

Fun fact: on Mono it returns

Def 5, RawDef 5
Def b, RawDef b

Answer

tl;dr: It's not a bug it's a feature...

As you can see in the documentation, RawDefaultValue supports reflection-only context while DefaultValue does not.

Now if we inspect the source code of both of the method we will see that it calls System.Reflection.MdConstant's method GetValue method with bool raw flag.

Since System.Reflection wants to provide you the 'best' information in can depending on the context it is in, it would rather provide you an enum instead of a raw value (The raw value can be concluded from the enum field, the opposite doesn't work).

Now we can see in System.Reflection.MdConstant.GetValue that:

if (fieldType.IsEnum && raw == false)
{
    ...
    switch (corElementType) //The actual enum value
    {
        ...
        case CorElementType.I4:
            defaultValue = *(int*)&buffer;
            break;
        ...
    }
    return RuntimeType.CreateEnum(fieldType, defaultValue);
}

Which in your case returns B.b // = 1.

But calling RawDefaultValue makes that flag false which makes it:

switch (corElementType)
{
    ...
    case CorElementType.I4:
        return *(int*)&buffer;
    ...
}

Which in your case returns 1.

If you try to invoke System.RuntimeType.CreateEnum using Reflection (because it's internal), with a reflection-only context loaded Assembly you will get an InvalidOperationException:

//Change to 'Assembly.Load' so the last line will not throw.
Assembly assembly = Assembly.ReflectionOnlyLoad("DllOfB");

Type runtimeType = Type.GetType("System.RuntimeType");
MethodInfo createEnum = runtimeType.GetMethod("CreateEnum", /*BindingFlags*/);

MethodInfo getRuntimeType = typeof(RuntimeTypeHandle).GetMethod("GetRuntimeType", /*BindingFlags*/);
Type bType = assembly.GetType("DllOfB.B");

//Throws 'InvalidOperationException':
object res = createEnum.Invoke(null, new [] { getRuntimeType.Invoke(bType.TypeHandle, new object[]{}), 1 });

As for Mono supporting the return of B.b from RawDefaultValue , It means that Mono doesn't support DefaultValue for reflection-only context or that it can somehow create an instance of a Type from an Assembly which is possibly in a different architecture - which is very unlikely.

Comments