为什么 Nullable<T> 被 PropertyInfo.SetValue 特殊处理



在实现类似于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.SetValueCopyOfNullable类型中失败而在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>

的一些细微差别。

相关内容

  • 没有找到相关文章

最新更新