【问题标题】:Why Nullable<T> is treated special by PropertyInfo.SetValue为什么 Nullable<T> 被 PropertyInfo.SetValue 特殊对待
【发布时间】:2015-08-31 21:32:36
【问题描述】:

在实现类似于Nullable&lt;T&gt; 的结构时,我发现PropertyInfo.SetValue 对待Nullable 类型的方式与其他类型不同。 对于 Nullable 属性,它可以设置底层类型的值

foo.GetType().GetProperty("NullableBool").SetValue(foo, true);

但对于自定义类型,它会抛出

System.ArgumentException:“SomeType”类型的对象无法转换为 NullableCase.CopyOfNullable 1[SomeType] 类型

即使所有转换运算符都以与原始 Nullable&lt;T&gt; 相同的方式被覆盖

要重现的代码:

   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&lt;T&gt; 类型通过?

【问题讨论】:

    标签: c# reflection type-conversion


    【解决方案1】:

    Nullable&lt;T&gt; 在 CLR 类型系统中具有特殊支持,可以自动从 T 转换。

    其实不可能有Nullable&lt;T&gt;的盒装实例;可空值框到基础值或实际空值。

    这是 BCL 中为数不多的魔法类型之一;无法复制。

    【讨论】:

    • 还有什么地方有这种特殊的支持?
    • 你的意思是CLR中还有哪些地方对Nullable&lt;T&gt;有特殊的支持,或者CLR还对哪些其他类型有特殊的支持?
    • @DenisPalnitsky:这比 CLR 更像 C#,但 decimal 类型不支持 constdecimal 不是 CLR 中的原始类型。编译器伪造const 支持(使用readonly),尽管这是一个略微泄漏的抽象。 Jon Skeet explains.
    • @Brian:事实上,有关 C# 编译器具有特殊知识的 所有 类型的列表,请参阅source.roslyn.io/#Microsoft.CodeAnalysis/…。同样,这与 CLR 完全分开。
    • @SLaks 我们还能从哪里获得这种特殊支持?
    【解决方案2】:

    调用.SetValue()时,调用树如下:

    • System.Reflection.RuntimePropertyInfo.SetValue(对象 obj,对象 值,对象 [] 索引)
    • System.Reflection.RuntimePropertyInfo.SetValue(对象 obj,对象 value, BindingFlags invokeAttr, Binder binder, Object[] index, CultureInfo 文化)
    • System.Reflection.RuntimeMethodInfo.Invoke(对象 obj,BindingFlags invokeAttr, Binder binder, Object[] 参数, CultureInfo 文化)
    • System.Reflection.RuntimeMethodInfo.InvokeArgumentsCheck(对象 obj, BindingFlags invokeAttr,Binder binder,Object[]参数, CultureInfo 文化)
    • System.Reflection.MethodBase.CheckArguments(Object[] 参数, Binder binder、BindingFlags invokeAttr、CultureInfoculture、Signature sig)
    • System.RuntimeType.CheckValue(对象值,Binder binder, CultureInfo 文化, BindingFlags invokeAttr)
    • System.RuntimeType.TryChangeType(对象值,Binder binder, CultureInfo 文化, Boolean needsSpecialCast) [只有在您使用自定义类型时才会调用它]

    不幸的是,当调用树到达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,这将允许该方法根据泛型类型值确定相等性。

    正如我们所知,Nullable 类型是特殊的并且有额外的语言支持(想想如何使用int? 而不是Nullable&lt;int&gt;)。当您的自定义类型遍历此相等检查时,它不会被视为相等实例,而是继续向下到逻辑树,将其视为单独的类型,并调用 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 类型在框架中具有无法复制的特殊支持。由于反射利用了这种支持,您将无法复制Nullable&lt;T&gt; 的一些细微差别

    【讨论】:

    • 您是说int? 不是由C# 语言翻译成Nullable&lt;int&gt;,而是一个CLR 函数来做到这一点?
    • @ClickRick 我已经查看了Roslyn Open Source Compiler Code,据我所知,编译器不会将int? 编译为Nullable&lt;Int&gt;,它们被保留为单独的语法节点。事实上,如果你反编译一个同时使用int?Nullable&lt;int&gt; 的应用程序,你可以看到编译器保留了语法。这让我相信? 是一个CLR 构造。
    • @MariusDornean:那是完全错误的。 int?Nullable&lt;T&gt; 的语法糖。 Roslyn 语法树始终保留原始语法,但语义模型将具有相同的类型。
    • 其实你可以看到Roslyn编译器代码实际上把T?变成Nullable&lt;T&gt;:source.roslyn.io/#Microsoft.CodeAnalysis.CSharp/Binder/…
    • @SLaks 感谢您的澄清!
    猜你喜欢
    • 2017-04-01
    • 2010-10-15
    • 1970-01-01
    • 2017-09-14
    • 2017-06-25
    • 1970-01-01
    • 1970-01-01
    • 2017-06-04
    相关资源
    最近更新 更多