C# 为什么为空<;T>;由PropertyInfo.SetValue进行特殊处理

C# 为什么为空<;T>;由PropertyInfo.SetValue进行特殊处理,c#,reflection,type-conversion,C#,Reflection,Type Conversion,在实现类似于Nullable的结构时,我发现PropertyInfo.SetValue对Nullable类型的处理与其他类型不同。 对于Nullable属性,它可以设置基础类型的值 foo.GetType().GetProperty("NullableBool").SetValue(foo, true); 但对于自定义类型,它抛出 System.ArgumentException:类型为“SomeType”的对象无法转换为类型NullableCase.CopyOfNullable 1[Some

在实现类似于
Nullable
的结构时,我发现
PropertyInfo.SetValue
Nullable
类型的处理与其他类型不同。 对于Nullable属性,它可以设置基础类型的值

foo.GetType().GetProperty("NullableBool").SetValue(foo, true);
但对于自定义类型,它抛出

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

即使所有转换运算符都以与原始
Nullable

要复制的代码:

   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 
        }
    }
}
使用系统;
命名空间NullableCase
{
/// 
///从.Net源代码复制可为null的
///为了简洁,没有不相关的方法
///     
公共结构CopyOfNullable,其中T:struct
{
私有布尔值;
内部T值;
可用的公共副本(T值)
{
这个值=值;
this.hasValue=true;
}
公共布尔值
{            
得到
{
返回值;
}
}
公共价值
{
得到
{
如果(!hasValue)
{
抛出新的InvalidOperationException();
}
返回值;
}
}
公共静态隐式运算符CopyOfNullable(T值)
{
返回新的CopyOfNullable(值);
}
公共静态显式运算符T(CopyOfNullable值)
{
返回值;
}
}
福班
{
公共可空NullableBool{get;set;}
public CopyOfNullableBool{get;set;}
}
班级计划
{
静态void Main(字符串[]参数)
{
Foo-Foo=新的Foo();
GetType().GetProperty(“NullableBool”).SetValue(foo,true);
foo.GetType().GetProperty(“CopyOfNullablobool”).SetValue(foo,true);//这里我们得到ArgumentException
}
}
}
为什么
PropertyInfo.SetValue
对于
CopyOfNullable
类型失败,并传递给
Nullable

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

事实上,不可能有一个可为null的装箱实例;“可为空的值”框设置为基础值或实际为空

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

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

  • System.Reflection.RuntimePropertyInfo.SetValue(对象obj,对象 值,对象[]索引)
  • System.Reflection.RuntimePropertyInfo.SetValue(对象obj,对象 值,BindingFlags invokeAttr,绑定器绑定器,对象[]索引, 文化信息(文化)
  • System.Reflection.RuntimeMethodInfo.Invoke(对象obj,BindingFlags invokeAttr,绑定器绑定器,对象[]参数,CultureInfo区域性)
  • System.Reflection.RuntimeMethodInfo.InvokeArgumentsCheck(对象obj, BindingFlags invokeAttr,Binder Binder,对象[]参数, 文化信息(文化)
  • System.Reflection.MethodBase.CheckArguments(对象[]参数, Binder Binder、BindingFlags invokeAttr、CultureInfo区域性、签名符号)
  • System.RuntimeType.检查值(对象值,活页夹 binder、CultureInfo区域性、BindingFlags invokeAttr)
  • System.RuntimeType.TryChangeType(对象值、绑定器、, CultureInfo区域性,布尔值需要特殊广播)[仅当您使用自定义类型时才会调用此选项]
不幸的是,当调用树到达时,它会检查对象是否是该类型的实例(在本例中为Bool)

Nullable将通过逻辑检查,因为
IsInstanceOfType(value)
返回true,框架将调用它,这将允许该方法基于泛型类型值确定相等性

正如我们所知,可空类型是特殊的,并且具有额外的语言支持(想想如何使用
int?
而不是
Nullable
)。当您的自定义类型遍历此相等检查时,它将不会被视为相等实例,而是继续向下到逻辑树,将其视为单独的类型,并调用
System.RuntimeType.TryChangeType

如果我们在
IsInstanceOfType
中进一步深入研究源代码,我们会发现
RuntimeTypeHandle.CanCastTo
用于确定相等性,并将键入委托给VM(在这种情况下,可空类型基于版本烘焙到VM中,因为框架中的可空类型被修饰为
[System.Runtime.Versioning.NonVersionable]


希望这告诉你的是,
Nullable
类型在框架中有特殊的支持,无法复制。由于反射利用了这种支持,你将无法复制
Nullable

的一些细微差别。你是说
int?
没有被C语言翻译成
Nullable吗
,这是一个CLR函数来实现吗?@ClickRick我已经查看了,从我所知道的,编译器不会将
int?
编译为
Nullable
,它们作为单独的语法节点保留。事实上,如果你反编译一个同时使用
int?
Nullable
的应用程序,你可以看到语法保留了编译器也是如此。这让我相信
是一个CLR构造。@MariusDornean:这完全错误。
int?
Nullable
的语法糖。Roslyn语法树始终保留原始语法,但语义模型将具有相同的类型。事实上,您可以看到实际使用的Roslyn编译器代码ly将
T?
转换为
Nullable
:@SLaks感谢您澄清这一点!除此之外,它还有什么特别的支持
    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;
            }
        }
   // 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);
        }