我对Nullable
类型的以下行为感到困惑:
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
的?
> smartcaveman 的答案是迄今为止最好的答案,因为它实际上标识了描述这种行为的文档部分。
这种行为是不可取和不幸的;这是由于行为结合了三个特征,而这三个特征本身的行为是合理的。
这三个功能是:
-
GetType
是一种非虚拟方法;它不能被覆盖。这应该是有道理的;对象无法决定其类型报告为什么。通过使其非虚拟,该方法可以保证说出真相。 -
传递给在
object
上声明的非虚拟方法的this
值必须转换为object
;因此,对于值类型的对象,对GetType()
调用的接收方被装箱到object
上。
可为 null 的值类型没有装箱形式;将可为 null 的 int 装箱时,要么获得带盒的 int,要么获得 null 引用。您永远不会获得对带框的可为空 int 的有效引用。
每个功能本身都是合理的,但组合起来结果是不希望的:当您在有效的可为空的 int 上调用 GetType
时,运行时会将可为空的 int 装箱到带盒的 int 中,然后将其作为object.GetType
的this
传递,当然会报告int
。如果该值是可为空的 int,则运行时框为 null,然后在空引用上调用 GetType
并崩溃。这两种行为都不是可取的,但我们坚持了下来。
可为空的类型有点奇怪。 但是,至少他们的行为是有据可查的。
来自 MSDN 上的 C# 编程指南,"如何:识别可为空的类型"at http://msdn.microsoft.com/en-us/library/ms366789(VS.80(.aspx :
还可以使用 System.Reflection 命名空间的类和方法来生成表示可为 Null 类型的 Type 对象。但是,如果尝试在运行时使用 GetType 方法或 is 运算符从可为 Null 的变量获取类型信息,则结果是表示基础类型的 Type 对象,而不是 Nullable 类型本身。在可为 Null 的类型上调用 GetType 会导致在类型隐式转换为 Object 时执行装箱操作。因此,GetType 始终返回一个表示基础类型的 Type 对象,而不是 Nullable 类型。
值得指出的是,标题中的区别是不准确的。 类型行为不是基于局部变量或属性而不同的,而是基于是通过运行时对象还是通过反射(或使用 typeof 运算符(访问类型。 您的推断是可以理解的,因为局部变量的类型通常只能通过运行时对象访问,但是,它是有缺陷的,因为如果您在运行时通过属性访问器访问可为 null 的对象,那么它的行为将等效于局部变量的行为。
另外,要明确回答问题的最后一部分:在不使用反射的情况下判断test.value为空的唯一方法是访问它并获得NullReferenceException(当然,这只有在test.value为null时才会发生。 如前所述,在您的示例中,该值不为 null,因此在没有反射的情况下确定该值是不可能的
问题是你假设test.value
的Type
与value
field.FieldType
的Type
相同,但事实并非如此。 获取test.value
的Type
实际上会获得字段中存储的内容的Type
,在本例中为 0。
Type t = test.value.GetType()
与(在您的示例中(相同
Type t = 0.GetType()
为了演示,将value
初始化为 null,test.value.GetType()
将引发NullReferenceException
方法 GetNulllableUnderlyingType 返回 基础类型 对于可为空的类型,对于另一种类型返回 null;
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;
}
}
首先,示例中Nullable.GetUnderlyingType(test.value)
将无法编译。或者typeof(test.value)
正如我在评论中看到的那样。
让我们通过以下修改后的代码来回答这个问题:
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
之前,您从不同类型的开始,最终会得到不同的结果。