`typeof(T).IsAssignableFrom(x.GetType())` 可以安全地重写为 `x is T` 吗?

Can `typeof(T).IsAssignableFrom(x.GetType())` be safely rewritten as `x is T`?

Note: This is not a duplicate of "Use of IsAssignableFrom and “is” keyword in C#". That other question asks about typeof(T).IsAssignableFrom(type)), where type is not an object but a Type.

This seems trivial — I can hear you saying, "Just call x.GetType()!" — but due to the COM-related corner case mentioned below, that call causes problems, which is why I'm asking about the rewrite.

…或者是否存在两者可能给出不同结果的罕见特殊情况?

我偶然发现了以下形式的类型检查:

typeof(TValue).IsAssignableFrom(value.GetType())

其中 TValue 是泛型类型参数(没有任何约束),valueobject.

我不完全确定将上面的内容简单地重写是否安全:

value is TValue

据我目前所知,除了 COM 对象外,这两个测试是等效的。 is 应该触发正确的 QueryInterface,而 IsAssignableFrom 可能会被 __ComObject RCW 包装器类型混淆并报告假阴性。

is 和显示的 IsAssignableFrom 之间还有其他区别吗?

    static void Main(string[] args)
    {
        int? bob = null;

        Test(bob);
    }

    private static void Test<T>(T bob)
    {
        Console.WriteLine(bob is T);
        Console.WriteLine(typeof(T).IsInstanceOfType(bob));
        Console.WriteLine(typeof(T).IsAssignableFrom(bob.GetType()));
        Console.ReadLine();
    }

是一个示例,其中它们的行为略有不同(因为 bob 为空)。 可能会感兴趣。

除此之外(以及您提到的其他例外情况)它们似乎是等价的。

isIsAssignableFrom returns 不同结果的情况更多,而不仅仅是您提到的 COM object 的情况。对于元素类型为 ElementType1ElementType2 的一对数组类型,其中两种元素类型的底层类型都是大小相同但大小相反的整数类型签名,然后

typeof(ElementType1[]).IsAssignableFrom(typeof(ElementType2[])) returns 正确

new ElementType2[0] is ElementType1[] returns

具体来说,这包括具有这些元素类型对的数组:

  • byte / sbyte, short / ushort, int / uint, long / ulong

  • IntPtr / UIntPtr

  • 枚举类型与整数类型或其他枚举类型的任意组合,只要底层类型大小相同

  • IntPtr/UIntPtr/int/[的任意组合=49=]uint 在 32 位进程中

  • IntPtr/UIntPtr/long/[的任意组合=49=]ulong 64位进程

这是由于 C# 和 CLR 类型系统的差异,如

中所述

在上述所有情况下 isIsAssignableFrom 的不同结果是因为对于 new ElementType2[0] is ElementType1[] C# 编译器只是在 compile-time 处发出 False (因为它认为无法将例如 int[] 转换为 uint[],因为从 C# 的角度来看它们是完全不同的类型),完全省略了任何运行时类型检查。幸运的是,将数组转换为 object ((object)new ElementType2[0]) is ElementType1[] 强制编译器发出 isinst IL 指令,该指令执行运行时 type-check,returns 结果与 IsAssignableFrom 一致。对于目标类型是泛型参数的情况也是如此,因为它的类型在 compile-time 处未知并且 C# 编译器必须发出 isinst。因此,如果您打算仅在目标类型为通用参数的地方(如问题标题中所建议的那样)将 IsAssignableFrom 替换为 is,我相信这些差异不适合您。