简体   繁体   中英

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

I have been diving deeper into concepts like reflection and RTTI. For example, recently I learned how dynamic_cast works under the hood in C++. This helped me understand why it's considered a slow operation (in general). I find it very helpful to think about the differences between C# (and .NET) and C++. This is how I ended up thinking about "operator is" and what exactly it does under the hood. What kinds of comparisons does it make? I assume it does something with "Type".

Cheers.

From the 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.

The crucial part is probably "D is the run-time type of the instance reference by E": in C# every reference type has a Type field that contains the type. All valid implicit reference conversions are defined in 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.
•   ...

There are a few more, but these are the most important conversions.

TL;DR; CIL has special instruction that provides this functionality.

For example we take this code:

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() so it can't be an instance of the entry point class.

When compiled re-created code looks like this:

// 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!");
}

When looking at generated IL code we can see this:

.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

Special instruction called isinst checks if your object's type is equal to type provided by you.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM