为什么 Nullable<T> 被 PropertyInfo.SetValue 特殊对待
Why Nullable<T> is treated special by PropertyInfo.SetValue
在实现类似于 Nullable<T>
的结构时,我发现 PropertyInfo.SetValue
对待 Nullable
类型的方式与其他类型不同。
对于 Nullable 属性,它可以设置基础类型的值
foo.GetType().GetProperty("NullableBool").SetValue(foo, true);
但对于自定义类型它抛出
System.ArgumentException: Object of type 'SomeType' cannot be converted to type 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>
的盒装实例;可空值框到基础值或实际空值。
这是BCL中为数不多的魔法类型之一;无法复制。
调用.SetValue()时,调用树如下:
- System.Reflection.RuntimePropertyInfo.SetValue(对象对象, 对象
值,对象 [] 索引)
- System.Reflection.RuntimePropertyInfo.SetValue(对象对象, 对象
值,BindingFlags invokeAttr,Binder 活页夹,Object[] 索引,
CultureInfo 文化)
- System.Reflection.RuntimeMethodInfo.Invoke(对象对象, BindingFlags
invokeAttr、Binder 绑定器、Object[] 参数、CultureInfo 文化)
- System.Reflection.RuntimeMethodInfo.InvokeArgumentsCheck(对象对象,
BindingFlags invokeAttr,Binder绑定器,Object[]参数,
CultureInfo 文化)
- System.Reflection.MethodBase.CheckArguments(对象[]参数,
Binder 活页夹、BindingFlags invokeAttr、CultureInfo 文化、签名 sig)
- System.RuntimeType.CheckValue(对象值,Binder
活页夹,CultureInfo 文化,BindingFlags invokeAttr)
- System.RuntimeType.TryChangeType(对象值, Binder 绑定器,
CultureInfo 文化,布尔值需要 SpecialCast) [仅在您使用自定义类型时调用]
不幸的是,当调用树到达 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;
}
}
Nullable 将作为 IsInstanceOfType(value)
returns true 通过逻辑检查,框架将调用 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>
的一些细微差别
在实现类似于 Nullable<T>
的结构时,我发现 PropertyInfo.SetValue
对待 Nullable
类型的方式与其他类型不同。
对于 Nullable 属性,它可以设置基础类型的值
foo.GetType().GetProperty("NullableBool").SetValue(foo, true);
但对于自定义类型它抛出
System.ArgumentException: Object of type 'SomeType' cannot be converted to type 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>
的盒装实例;可空值框到基础值或实际空值。
这是BCL中为数不多的魔法类型之一;无法复制。
调用.SetValue()时,调用树如下:
- System.Reflection.RuntimePropertyInfo.SetValue(对象对象, 对象 值,对象 [] 索引)
- System.Reflection.RuntimePropertyInfo.SetValue(对象对象, 对象 值,BindingFlags invokeAttr,Binder 活页夹,Object[] 索引, CultureInfo 文化)
- System.Reflection.RuntimeMethodInfo.Invoke(对象对象, BindingFlags invokeAttr、Binder 绑定器、Object[] 参数、CultureInfo 文化)
- System.Reflection.RuntimeMethodInfo.InvokeArgumentsCheck(对象对象, BindingFlags invokeAttr,Binder绑定器,Object[]参数, CultureInfo 文化)
- System.Reflection.MethodBase.CheckArguments(对象[]参数, Binder 活页夹、BindingFlags invokeAttr、CultureInfo 文化、签名 sig)
- System.RuntimeType.CheckValue(对象值,Binder 活页夹,CultureInfo 文化,BindingFlags invokeAttr)
- System.RuntimeType.TryChangeType(对象值, Binder 绑定器, CultureInfo 文化,布尔值需要 SpecialCast) [仅在您使用自定义类型时调用]
不幸的是,当调用树到达 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;
}
}
Nullable 将作为 IsInstanceOfType(value)
returns true 通过逻辑检查,框架将调用 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>