【问题标题】:Nullable properties vs. Nullable local variables可空属性与可空局部变量
【发布时间】:2013-04-29 04:39:19
【问题描述】:

我对@9​​87654321@类型的以下行为感到困惑:

class TestClass {
    public int? value = 0;
}

TestClass test = new TestClass();

现在,Nullable.GetUnderlyingType(test.value) 返回底层的Nullable 类型,即int。 但是,如果我尝试获取这样的字段类型

FieldInfo field = typeof(TestClass).GetFields(BindingFlags.Instance | BindingFlags.Public)[0];

然后我调用

Nullable.GetUnderlyingType(field.FieldType).ToString()

它返回一个System.Nullable[System.Int32] 类型。这意味着Nullable.GetUnderlyingType() 方法具有不同的行为,具体取决于您获取成员类型的方式。为什么呢?如果我只是使用test.value,我怎么知道它是Nullable而不使用反射?

【问题讨论】:

  • typeof(field.FieldType) == typeof(test.value)?我可以想象,由于该方法称为GetUnderlyingType,因此它对类型和类型的实例的作用不同。
  • 不,typeof(field.FieldType) 返回Nullable<int>,而typeof(test.value) 只是返回System.Int32——我在问,为什么它们返回不同的类型?此外,如何检查test.value 是否为Nullable
  • 你试过test.value is Nullable<int>吗?尝试int 以确保它不会给出误报。 IMO 的奇怪行为来自 typeof(test.value) 返回 System.Int32 而不是 Nullable<int> 的奇怪事实。
  • test.value 根据定义总是返回一个int
  • 什么意思? test.value is Nullable<int> 是一个检查,它返回一个 bool,指示 test.value 是否是 Nullable<int> 的一个实例。

标签: c# reflection nullable


【解决方案1】:

问题是您假设test.valueTypevaluefield.FieldTypeType 相同,但事实并非如此。获取test.valueType 实际上得到了字段中存储的Type,在本例中为0。

Type t = test.value.GetType()

与(在您的示例中)相同

Type t = 0.GetType()

为了演示,将value初始化为null,test.value.GetType()将抛出NullReferenceException

【讨论】:

    【解决方案2】:

    可空类型有点奇怪。但是,至少他们的行为是有据可查的。

    来自 MSDN 上的 C# 编程指南, “如何:识别可空类型”http://msdn.microsoft.com/en-us/library/ms366789(VS.80).aspx

    您还可以使用 System.Reflection 命名空间的类和方法来生成表示 Nullable 类型的 Type 对象。但是,如果您尝试在运行时使用 GetType 方法或 is 运算符从 Nullable 变量获取类型信息,则结果是表示基础类型的 Type 对象,而不是 Nullable 类型本身。 

    对 Nullable 类型调用 GetType 会导致在类型隐式转换为 Object 时执行装箱操作。因此 GetType 总是返回一个表示基础类型的 Type 对象,而不是 Nullable 类型。

    值得指出的是,您的标题中的区别是不准确的。类型行为不是基于局部变量或属性来区分的,而是取决于该类型是通过运行时对象还是通过反射(或使用 typeof 运算符)来访问的。你的推论是可以理解的,因为局部变量的类型通常只能通过运行时对象来访问,但是,它是有缺陷的,因为如果你在运行时通过属性访问器访问一个可为空的对象,那么它的行为将等同于一个局部变量。

    另外,要明确回答您问题的最后一部分:在不使用反射的情况下判断 test.value 可以为空的唯一方法是访问它并获取 NullReferenceException(当然, , 只有在 test.value 为空时才会发生。如您所写,在您的示例中,值不为空,因此在没有反射的情况下确定这一点是不可能的

    【讨论】:

    • 感谢您提供实际测试过程!
    【解决方案3】:

    GetNulllableUnderlyingType 方法为可空类型返回底层类型,对于其他类型返回空;

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(GetNulllableUnderlyingType(new TestClass().value));
            Console.WriteLine(GetNulllableUnderlyingType(new TestClass()));
            Console.ReadKey();
        }
    
        public class TestClass
        {
            public int? value;
        }
    
        public static Type GetNulllableUnderlyingType<T>(T value)
        {
            Type result =  Nullable.GetUnderlyingType(typeof (T));
            return result;
        }
    }
    

    【讨论】:

    • 是的,我在示例中使用了错误的BindingFlags,但在我正在测试的代码中使用了正确的BindingFlags。感谢您指出。
    • 但我仍然有正确的输出。我仍然确定您的示例有问题。
    【解决方案4】:

    首先,您的示例中的Nullable.GetUnderlyingType(test.value) 将无法编译。或者typeof(test.value),就像我在 cmets 中看到的那样。

    让我们通过以下修改后的代码来回答这个问题:

    TestClass test = new TestClass();
    Type type1 = test.value.GetType(); // Gets the type of the data inside the field ==> System.Int32.
    Type underlyingType1 = Nullable.GetUnderlyingType(type1); // Gets the underlying type of System.Int32 ==> null.
    
    FieldInfo field = typeof(TestClass).GetFields(BindingFlags.Instance | BindingFlags.Public)[0];
    Type type2 = field.FieldType; // Now the type is retrieved of the field, not the data inside. ==> System.Int32?
    Type underlyingType2 = Nullable.GetUnderlyingType(type2); // Gets the underlying type of System.Int32? ==> System.Int32.
    

    当你对一个字段执行GetType() 时,你会得到里面数据的类型,而不是字段本身的类型。例如:

    object o = 1;
    Type type = o.GetType();
    

    在这个例子中,当你调用GetType(),你也会得到System.Int32,而不是System.Object。因为在调用GetUnderylingType 之前,您开始使用不同的类型,最终会得到不同的结果。

    【讨论】:

      【解决方案5】:

      smartcaveman 的答案是迄今为止最好的答案,因为它实际上标识了描述此行为的文档部分。

      这种行为是不受欢迎和不幸的;这是由于三个特征组合在一起的行为,这些特征本身就表现得很合理。

      三个特点是:

      • GetType是非虚方法;它不能被覆盖。这应该是有道理的;对象无法决定其报告的类型。通过使其成为非虚拟方法,可以保证该方法是真实的。

      • 传递给在object 上声明的非虚拟方法的this 值必须转换为object;因此,对于值类型的对象,对GetType() 的调用的接收者被装箱到object

      • 可空值类型没有装箱形式;当你装箱一个可为空的 int 时,你要么得到一个装箱的 int,要么你得到一个空引用。您永远不会获得对盒装可空 int 的有效引用。

      每个功能本身都是合理的,但组合起来的结果是不可取的:当您在有效的可为空 int 上调用 GetType 时,运行时将可为空的 int 装箱到装箱的 int,然后将其作为 this 传递object.GetType 当然报告int。如果该值为 null 可为空的 int,则运行时框为 null,然后在 null 引用上调用 GetType 并崩溃。这些行为都不是可取的,但我们被他们困住了。

      【讨论】:

      • 谢谢。我读过很多关于 .NET 一般如何处理空值(不仅仅是可空值)的批评。例如我相信您已经听说过 default(object) == null 是一个固有缺陷。您是否在任何地方专门解决了这一争论,以及设计决策的根本原因?
      • @smartcaveman:default(object) 你还会选择什么?如果你讨厌 null,请随意使用 Jon Skeet 的 NonNullable&lt;T&gt;
      • @Brian:对于类类型,最好有一个更有意义的默认值。对于stringList&lt;T&gt;,空字符串和只读空列表是比null 更好的默认值。 (虽然很难说如何处理接口和委托。)在这方面将传统 OOP 语言与原型继承语言进行对比很有趣。
      • @Brian,我会选择new {} 之类的东西,但理想情况下它会被实习,所以你总是会得到相同的参考。我敢肯定 Eric 比我给出的更多。我还认为,如果您可以通过某种原生 IoC 为接口和委托配置默认值,那将非常酷……这似乎是未配置委托的默认值理想情况下什么都不做,然后返回返回类型的默认值(如果不是 void)。接口可以采用相同类型的方法。 (当然,这在游戏的这个阶段显然是不可能的)
      • 问题本质上是“默认”的意思是“这种类型的字段或数组元素应该如何被内存分配器初始化”,实际上并不是语义上默认的.
      猜你喜欢
      • 1970-01-01
      • 2015-04-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-11-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多