在实现类似于Nullable<T>
的结构时,我发现PropertyInfo.SetValue
对待Nullable
类型不同于其他类型。对于Nullable属性,它可以设置底层类型
foo.GetType().GetProperty("NullableBool").SetValue(foo, true);
但是对于自定义类型,它抛出
系统。ArgumentException:类型'SomeType'的对象不能转换为类型NullableCase。CopyOfNullable 1 (SomeType)
,即使所有转换操作符都以与原始Nullable<T>
相同的方式重写
复制代码:
using System;
namespace NullableCase
{
/// <summary>
/// Copy of Nullable from .Net source code
/// without unrelated methodts for brevity
/// </summary>
public struct CopyOfNullable<T> where T : struct
{
private bool hasValue;
internal T value;
public CopyOfNullable(T value)
{
this.value = value;
this.hasValue = true;
}
public bool HasValue
{
get
{
return hasValue;
}
}
public T Value
{
get
{
if (!hasValue)
{
throw new InvalidOperationException();
}
return value;
}
}
public static implicit operator CopyOfNullable<T>(T value)
{
return new CopyOfNullable<T>(value);
}
public static explicit operator T(CopyOfNullable<T> value)
{
return value.Value;
}
}
class Foo
{
public Nullable<bool> NullableBool { get; set; }
public CopyOfNullable<bool> CopyOfNullablBool { get; set; }
}
class Program
{
static void Main(string[] args)
{
Foo foo = new Foo();
foo.GetType().GetProperty("NullableBool").SetValue(foo, true);
foo.GetType().GetProperty("CopyOfNullablBool").SetValue(foo, true); //here we get ArgumentException
}
}
}
为什么PropertyInfo.SetValue
在CopyOfNullable
类型中失败而在Nullable<T>
类型中通过?
Nullable<T>
在CLR类型系统中有特殊的支持,可以自动从T
转换。
实际上,不可能有Nullable<T>
的盒装实例;为基础值或实际null。
这是BCL中为数不多的神奇类型之一;
调用.SetValue()时,调用树如下:
- System.Reflection.RuntimePropertyInfo。SetValue(对象对象值,对象[]索引)
- System.Reflection.RuntimePropertyInfo。SetValue(对象对象绑定对象[]索引。CultureInfo文化)
- System.Reflection.RuntimeMethodInfo。调用对象obj, BindingFlags
- System.Reflection.RuntimeMethodInfo。InvokeArgumentsCheck(对象obj,BindingFlags invokeAttr, Binder, Binder, Object[]参数,CultureInfo culture)
- System.Reflection.MethodBase。CheckArguments (Object[]参数,Binder Binder, BindingFlags invokeAttr, CultureInfo文化,签名签名
- System.RuntimeType。CheckValue(对象值,Binder
- System.RuntimeType。(对象值,绑定器,绑定器)
不幸的是,当调用树到达RuntimeType.CheckValue
时,它检查对象是否是该类型的实例(在本例中为Bool)。
RuntimeType runtimeType;
if (this.IsInstanceOfType(value))
{
Type type = null;
RealProxy realProxy = RemotingServices.GetRealProxy(value);
type = (realProxy == null ? value.GetType() : realProxy.GetProxiedType());
if (type == this || !RuntimeTypeHandle.IsValueType(this))
{
return value;
}
return RuntimeType.AllocateValueType(this, value, true);
}
if (!base.IsByRef)
{
if (value == null)
{
return value;
}
if (this == RuntimeType.s_typedRef)
{
return value;
}
}
当IsInstanceOfType(value)
返回true时,Nullable将通过逻辑检查,并且框架将调用RemotingServices.GetRealProxy
,这将允许方法根据泛型类型值确定相等性。
如我们所知,可空类型是特殊的,有额外的语言支持(想想如何使用int?
而不是Nullable<int>
)。当您的自定义类型遍历这个相等性检查时,它将不被视为一个相等的实例,而是继续向下到逻辑树,将其视为一个单独的类型,并调用System.RuntimeType.TryChangeType
如果我们在IsInstanceOfType
中进一步深入研究源代码,我们会发现RuntimeTypeHandle.CanCastTo
用于确定相等性,并将类型委托给VM(在这种情况下,Nullable类型根据版本被装入VM,因为框架中的Nullable被装饰为[System.Runtime.Versioning.NonVersionable]
)
// For runtime type, let the VM decide.
if (fromType != null)
{
// both this and c (or their underlying system types) are runtime types
return RuntimeTypeHandle.CanCastTo(fromType, this);
}
希望这告诉你的是Nullable
类型在框架中有特殊的支持,这是无法复制的。由于Reflection利用了这种支持,因此您将无法复制Nullable<T>