为什么GetType返回System.Int32而不是Nullable<Int32>?

29
为什么这段代码的输出是System.Int32而不是Nullable<Int32>
int? x = 5;
Console.WriteLine(x.GetType());
4个回答

30

GetType()object 的一个方法。
要调用它,必须将 Nullable<T> 结构体装箱。

您可以在 IL 代码中看到这一点:

//int? x = 5;
IL_0000:  ldloca.s    00 
IL_0002:  ldc.i4.5    
IL_0003:  call        System.Nullable<System.Int32>..ctor

//Console.WriteLine(x.GetType());
IL_0008:  ldloc.0     
IL_0009:  box         System.Nullable<System.Int32>
IL_000E:  callvirt    System.Object.GetType
IL_0013:  call        System.Console.WriteLine

可空类型在CLR中被特殊对待;不可能有可空类型的装箱实例。相反,装箱可空类型将导致空引用(如果HasValue为false),或者装箱值(如果存在值)。
因此,box System.Nullable<System.Int32>指令的结果是装箱的Int32,而不是装箱的Nullable<Int32>
因此,GetType()永远不可能返回Nullable<T>
为了更清楚地看到这一点,请看下面的代码:
static void Main()
{
    int? x = 5;
    PrintType(x);   
}
static void PrintType<T>(T val) {
    Console.WriteLine("Compile-time type: " + typeof(T));
    Console.WriteLine("Run-time type: " + val.GetType());
}

这会打印出来:

编译时类型:System.Nullable`1[System.Int32]
运行时类型:System.Int32


9

GetType()不是虚拟的,因此仅在object上定义。因此,要进行调用,必须首先将Nullable<Int32>封箱。但是,Nullables具有特殊的封箱规则,因此仅封箱Int32值,并报告该类型。


1
是否有特定的原因,为什么 Nullable<T>(以及其他结构)不应该定义自己的 GetType 方法来遮蔽 Object 的方法?如果 Foo 是值类型,那么 Foo.GetType() 需要创建一个新的堆对象来存储 Foo 的装箱内容,而实际上这个内容将被忽略,这似乎非常愚蠢。难道只是将 Foo.GetType() 遮蔽为一个简单返回适当 Type 对象的方法不更有效率吗? - supercat
@supercat 我不确定我有资格回答那个问题。我会说的一件事是,可为空的结构在很多方面都很奇怪,这归因于它们实际上是事后添加的,而不是从开始就纳入语言中。总的来说,我想这个观点仍然成立,因为你需要将 int 变量装箱才能调用 GetType()。由于这种情况下可能涉及到的类型始终会被加载,因此这可能是一种空间/文档一致性的让步。也许 Eric Lippert 可以提供更多的信息。 - dlev
1
GetType() 对于 sealed 类型来说并没有太多意义,因为你可以直接使用 typeof()。像所有的 struct 一样,Nullable<T> 是隐式地被封闭的。如果调用 GetType() 并不是很有意义,当然,如果没有任何理由不这样做,它会正常运行,但是请不要期望过多的优化工作。 - user743382
@hvd:泛型的存在意味着有很多类型实际上是sealed(密封的),但使用它们的代码在静态上不知道它们是sealed。然而,在我写完之前的问题后,我想出了GetType按其方式工作的原因:CLR中每个声明的值类型实际上代表两种类型:未装箱的值类型和装箱堆类型。前一种类型仅用于描述存储位置,后者用于对象实例。存储在值类型存储位置中的东西只是位的组合,不属于类型系统。 - supercat

4

您不能 box 一个可空对象。

您可以像这样处理:

public static Type GetCompilerType<T>(this T @object)
{
  return typeof (T);
}

int? x = 5;
Console.WriteLine(x.GetCompilerType());
// prints:
// System.Nullable`1[System.Int32]

标识符未找到;'object' 是一个关键字。 - SLaks
@slaks,谢谢...忘记修复了。 - agent-j

1

因为 "5" 的类型是 int。

如果您想检测一个类型是否可为空,并获取其基础类型,请使用类似以下的代码:

public static Type GetActualType(Type type, out bool isNullable)
{
    Type ult = Nullable.GetUnderlyingType(type);
    if (ult != null)
    {
        isNullable = true;
        return ult;
     }
     isNullable = false;
     return type;
}

那不正确,SLaks。请看这里的文档:http://msdn.microsoft.com/en-us/library/system.nullable.getunderlyingtype.aspx,其中写道:“返回值类型:System.Type如果nullableType参数是一个封闭的泛型可空类型,则为nullableType参数的类型参数;否则为null。” - Ed Bayiates
你是对的,我记错了。然而,这并没有回答问题。x确实是一个int? - SLaks
是的,但它在一个 int? 变量中。你看到 int 的原因是 int? 被装箱为 int - SLaks
Slaks,我觉得我们的意思是一样的。你只是表达得更完整,但你原始评论中关于Nullable.GetUnderlyingType的描述也带有不必要的攻击性。 - Ed Bayiates

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接