枚举参数的 DefaultValue 和 RawDefaultValue 的意外差异
Unexpected difference in DefaultValue and RawDefaultValue for Enum parameters
考虑以下示例:
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
}
根据RawDefaultValue
and DefaultValue
, with support from Whosebug的文档,这两种访问默认值的方法应该return相同的数据。
但是,我得到以下输出:
Def 5, RawDef 5
Def b, RawDef 1
所以,显然,RawDefaultValue
删除了关于参数是枚举类型的信息。
我的问题:这是一个错误还是文档的另一部分证明了这一点?
有趣的事实:在 Mono 上 returns
Def 5, RawDef 5
Def b, RawDef b
从 .NET 参考源来看,这一切似乎都是有目的的:
public override Object DefaultValue { get { return GetDefaultValue(false); } }
public override Object RawDefaultValue { get { return GetDefaultValue(true); } }
private Object GetDefaultValue(bool raw)
{
// ... Precheck
object defaultValue = GetDefaultValueInternal(raw);
// ... Postprocessing
return defaultValue;
}
[System.Security.SecuritySafeCritical]
private Object GetDefaultValueInternal(bool raw)
{
// ... Too long to include, but it contains "if (raw)"
// ... a couple of times to do different things.
return defaultValue;
}
来源:
https://referencesource.microsoft.com/#mscorlib/system/reflection/parameterinfo.cs,565
现在 .NET 是开源的,如果您认为它应该以其他方式运行,只需进行修复并提交 Pull Request ;-)
tl;dr:这不是错误,而是一个功能...
正如您在文档中看到的那样,RawDefaultValue
supports reflection-only context while DefaultValue
没有。
现在,如果我们检查这两个方法的源代码,我们会看到它调用了 System.Reflection.MdConstant
的方法 GetValue
带有 bool raw
标志的方法。
由于 System.Reflection
想根据上下文为您提供 can 中的 'best' 信息,因此它宁愿为您提供 enum
而不是原始值(原始值可以从enum
字段得出,反之则不行。
现在我们可以在 System.Reflection.MdConstant.GetValue
中看到:
if (fieldType.IsEnum && raw == false)
{
...
switch (corElementType) //The actual enum value
{
...
case CorElementType.I4:
defaultValue = *(int*)&buffer;
break;
...
}
return RuntimeType.CreateEnum(fieldType, defaultValue);
}
你的情况 returns B.b // = 1
.
但是调用 RawDefaultValue
使标志 false
成为:
switch (corElementType)
{
...
case CorElementType.I4:
return *(int*)&buffer;
...
}
你的情况 returns 1
.
如果您尝试使用 Reflection
调用 System.RuntimeType.CreateEnum
(因为它是内部的),并且 仅反射上下文 已加载 Assembly
您将得到 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 });
至于Mono
从RawDefaultValue
支持B.b
的return,表示Mono
不支持 DefaultValue
仅反射上下文 或者它可以以某种方式从 Assembly
创建一个 Type
的实例,它可能在不同的架构 - 这不太可能。
考虑以下示例:
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
}
根据RawDefaultValue
and DefaultValue
, with support from Whosebug的文档,这两种访问默认值的方法应该return相同的数据。
但是,我得到以下输出:
Def 5, RawDef 5
Def b, RawDef 1
所以,显然,RawDefaultValue
删除了关于参数是枚举类型的信息。
我的问题:这是一个错误还是文档的另一部分证明了这一点?
有趣的事实:在 Mono 上 returns
Def 5, RawDef 5
Def b, RawDef b
从 .NET 参考源来看,这一切似乎都是有目的的:
public override Object DefaultValue { get { return GetDefaultValue(false); } }
public override Object RawDefaultValue { get { return GetDefaultValue(true); } }
private Object GetDefaultValue(bool raw)
{
// ... Precheck
object defaultValue = GetDefaultValueInternal(raw);
// ... Postprocessing
return defaultValue;
}
[System.Security.SecuritySafeCritical]
private Object GetDefaultValueInternal(bool raw)
{
// ... Too long to include, but it contains "if (raw)"
// ... a couple of times to do different things.
return defaultValue;
}
来源:
https://referencesource.microsoft.com/#mscorlib/system/reflection/parameterinfo.cs,565
现在 .NET 是开源的,如果您认为它应该以其他方式运行,只需进行修复并提交 Pull Request ;-)
tl;dr:这不是错误,而是一个功能...
正如您在文档中看到的那样,RawDefaultValue
supports reflection-only context while DefaultValue
没有。
现在,如果我们检查这两个方法的源代码,我们会看到它调用了 System.Reflection.MdConstant
的方法 GetValue
带有 bool raw
标志的方法。
由于 System.Reflection
想根据上下文为您提供 can 中的 'best' 信息,因此它宁愿为您提供 enum
而不是原始值(原始值可以从enum
字段得出,反之则不行。
现在我们可以在 System.Reflection.MdConstant.GetValue
中看到:
if (fieldType.IsEnum && raw == false)
{
...
switch (corElementType) //The actual enum value
{
...
case CorElementType.I4:
defaultValue = *(int*)&buffer;
break;
...
}
return RuntimeType.CreateEnum(fieldType, defaultValue);
}
你的情况 returns B.b // = 1
.
但是调用 RawDefaultValue
使标志 false
成为:
switch (corElementType)
{
...
case CorElementType.I4:
return *(int*)&buffer;
...
}
你的情况 returns 1
.
如果您尝试使用 Reflection
调用 System.RuntimeType.CreateEnum
(因为它是内部的),并且 仅反射上下文 已加载 Assembly
您将得到 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 });
至于Mono
从RawDefaultValue
支持B.b
的return,表示Mono
不支持 DefaultValue
仅反射上下文 或者它可以以某种方式从 Assembly
创建一个 Type
的实例,它可能在不同的架构 - 这不太可能。