"operator is" 在 C# 中是如何工作的?

How does "operator is" work under the hood in C#?

我一直在深入研究反射和 RTTI 等概念。例如,最近我了解到 dynamic_cast 在 C++ 中是如何工作的。这帮助我理解了为什么它被认为是一个缓慢的操作(通常)。我发现思考 C#(和 .NET)和 C++ 之间的差异非常有帮助。这就是我最终想到 "operator is" 的方式,以及它到底做了什么。它做了什么样的比较?我假设它用 "Type".

做了一些事情

干杯。

来自C# language specification

7.10.10 The is operator
The is operator is used to dynamically check if the run-time type of an object is compatible with a given type. The result of the operation E is T, where E is an expression and T is a type, is a boolean value indicating whether E can successfully be converted to type T by a reference conversion, a boxing conversion, or an unboxing conversion. The operation is evaluated as follows, after type arguments have been substituted for all type parameters:
•   If E is an anonymous function, a compile-time error occurs
•   If E is a method group or the null literal, of if the type of E is a reference type or a nullable type and the value of E is null, the result is false.
•   Otherwise, let D represent the dynamic type of E as follows:
  o If the type of E is a reference type, D is the run-time type of the instance reference by E.
  o If the type of E is a nullable type, D is the underlying type of that nullable type.
  o If the type of E is a non-nullable value type, D is the type of E.
•   The result of the operation depends on D and T as follows:
  o If T is a reference type, the result is true if D and T are the same type, if D is a reference type and an implicit reference conversion from D to T exists, or if D is a value type and a boxing conversion from D to T exists.
  o If T is a nullable type, the result is true if D is the underlying type of T.
  o If T is a non-nullable value type, the result is true if D and T are the same type.
  o Otherwise, the result is false.
Note that user defined conversions, are not considered by the is operator.

关键部分可能是 "D is the run-time type of the instance reference by E":在 C# 中,每个引用类型都有一个包含该类型的 Type 字段。 所有有效的隐式引用转换都在 6.1.6:

中定义
The implicit reference conversions are:
•   From any reference-type to object and dynamic.
•   From any class-type S to any class-type T, provided S is derived from T.
•   From any class-type S to any interface-type T, provided S implements T.
•   From any interface-type S to any interface-type T, provided S is derived from T.
•   ...

还有一些,但这些是最重要的转化。

TL;DR; CIL 有提供此功能的特殊指令。

例如我们采用这段代码:

var obj = GetObject();

if (obj is Program)
{
       Console.WriteLine("How did we end up here?");
}
else
{
       Console.WriteLine("It can't be main class!");
}

GetObject() returns new object() 所以它不能是入口点的实例 class.

编译后重新创建的代码如下所示:

// Token: 0x0600000D RID: 13 RVA: 0x0000233B File Offset: 0x0000053B
[STAThread]
private static void Main()
{
    if (Program.GetObject() is Program)
    {
        Console.WriteLine("How did we end up here?");
        return;
    }

    Console.WriteLine("It can't be main class!");
}

查看生成的 IL 代码时,我们可以看到:

.method private hidebysig static 
void Main () cil managed 
{
.custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = (
01 00 00 00
)
    // Header Size: 1 byte
    // Code Size: 34 (0x22) bytes
    .maxstack 8
    .entrypoint

    /* 0x0000053C 280C000006   */ IL_0000: call      object Test.Program::GetObject()
    /* 0x00000541 7503000002   */ IL_0005: isinst    Test.Program
    /* 0x00000546 2C0B         */ IL_000A: brfalse.s IL_0017

    /* 0x00000548 7208020070   */ IL_000C: ldstr     "How did we end up here?"
    /* 0x0000054D 283800000A   */ IL_0011: call      void [mscorlib]System.Console::WriteLine(string)
    /* 0x00000552 2A           */ IL_0016: ret

    /* 0x00000553 7238020070   */ IL_0017: ldstr     "It can't be main class!"
    /* 0x00000558 283800000A   */ IL_001C: call      void [mscorlib]System.Console::WriteLine(string)

    /* 0x0000055D 2A           */ IL_0021: ret
} // end of method Program::Main

名为 isinst 的特殊指令检查您的对象的类型是否等于您提供的类型。