C# 可空类型不是可空类型吗?

C# 可空类型不是可空类型吗?,c#,.net,nullable,gettype,C#,.net,Nullable,Gettype,我用可空类型进行了一些测试,但它并没有像我预期的那样工作: int? testInt = 0; Type nullableType = typeof(int?); Assert.AreEqual(nullableType, testInt.GetType()); // not the same type 这也不起作用: DateTime? test = new DateTime(434523452345); Assert.IsTrue(test.GetType() == typeof(Null

我用可空类型进行了一些测试,但它并没有像我预期的那样工作:

int? testInt = 0;
Type nullableType = typeof(int?);
Assert.AreEqual(nullableType, testInt.GetType()); // not the same type
这也不起作用:

DateTime? test = new DateTime(434523452345);
Assert.IsTrue(test.GetType() == typeof(Nullable)); //FAIL 

DateTime? test = new DateTime(434523452345);
Assert.IsTrue(test.GetType() == typeof(Nullable<>)); //STILL FAIL
我的问题是为什么testInt.GetType返回int,而typeOfIt返回int?返回真正的可为空类型?

根据:

对可为空的类型调用GetType 使装箱操作失败 当类型为隐式时执行 转换为对象。因此GetType 始终返回一个 表示基础类型,而不是 可为空的类型

当您装箱一个可为空的对象时,只装箱基础类型

同样,来自:

装箱一个非空的可空值类型 框中的值类型本身,而不是 包装该值的System.Nullable 类型


根据Romain的正确答案,如果您想要比较实类型,即不隐式地将任何可空类型转换为其基础类型,那么您可以创建如下扩展方法:

public static class MyExtensionMethods
{
    public static Type GetRealType<T>(this T source)
    {
        return typeof(T);
    }
}
编辑

为了完整性——并根据SLaks下面的评论——这里有一个替代版本,它只在源代码为null或可为null时使用编译时类型;否则,它将使用GetType并返回运行时类型:

public static class MyExtensionMethods
{
    public static Type GetRealType<T>(this T source)
    {
        Type t = typeof(T);

        if ((source == null) || (Nullable.GetUnderlyingType(t) != null))
            return t;

        return source.GetType();
    }
}

虽然C假装值类型存储位置包含从System.ValueType派生的类型的实例,而System.ValueType又派生自System.Object,但事实并非如此。从System.ValueType派生的每个类型实际上代表两种截然不同的类型:

字节的集合,对于基本类型,字节直接表示数据;对于非基本结构类型,字节集合包含所有字段(公共字段和私有字段)的内容,但不包含任何类型信息。 一个独立的堆对象,除上述对象外还包含一个对象头,其类型派生自“System.ValueType”。 值类型的存储位置保存第一个值;值类型的堆对象持有第二个值

出于各种原因,Microsoft决定Nullable只支持第一次使用。如果试图将类型为Nullable的存储位置传递给需要对堆对象进行引用的代码,则如果HasValue为true,系统将该项转换为T;否则,如果HasValue为false,则仅传递null引用。虽然有一些方法可以创建类型为Nullable的堆对象,但将值类型存储位置转换为堆对象的常规方法永远不会生成值类型存储位置


还要注意,对值存储位置调用GetType实际上不会计算存储位置的类型,而是将该存储位置的内容转换为堆对象,然后返回结果对象的类型。由于类型为Nullable的存储位置被转换为T或null的对象实例,因此对象实例中的任何内容都不会说明它所来自的存储位置是否可为Nullable。

使用is运算符检查的简单方法:

(i is Nullable<int>) || (i is Nullable<long>) || (i is Nullable<float>) || (i is Nullable<short>)
我发现ti正在阅读这两个MSDN页面:


干杯

请注意,这将获得编译时类型。@SLaks:是否有过nullable的运行时类型与编译时类型不同的情况?我很感激您可以将任何类型传递给这个方法,在这种情况下,您确实可以获得运行时类型,但根据问题,它的目的是获得可为null的实际类型。您是对的。int?2.GetRealType实际上并不重要。objectnullable.GetRealType不明确。@SLaks:Oops,我的意思是……在这种情况下,您确实会得到编译时类型。而且,是的,当您调用objectnullable.GetRealType时,您不再传递nullable。您正在传递nullable的基础类型的装箱值,或者只传递纯null。您可以提到,为了使用第二个版本,不能装箱nullable变量。另外,将您的第一个版本重命名为GetCompileType可以清楚地了解此方法的真正功能。它的伸缩性不是很好,是吗?可为空的、可为空的等
(i is Nullable<int>) || (i is Nullable<long>) || (i is Nullable<float>) || (i is Nullable<short>)