13

I was wondering how is is operator implemented in C#.And I have written a simple test program (nothing special, just for demonstration purposes):

class Base
{
    public void Display() {  Console.WriteLine("Base"); }
}

class Derived : Base { }

class Program
{
    static void Main(string[] args)
    {
        var d = new Derived();

        if (d is Base)
        {
            var b = (Base) d;
            d.Display();
        }
    }
}

And looked at the generated IL code:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       27 (0x1b)
  .maxstack  2
  .locals init ([0] class ConsoleApplication1.Derived d,
           [1] bool V_1,
           [2] class ConsoleApplication1.Base b)
  IL_0000:  nop
  IL_0001:  newobj     instance void ConsoleApplication1.Derived::.ctor()
  IL_0006:  stloc.0  // set derived (d)
  IL_0007:  ldloc.0 // load derived
  IL_0008:  ldnull // push a null reference
  IL_0009:  ceq   // and compare with d !?
  IL_000b:  stloc.1
  IL_000c:  ldloc.1
  IL_000d:  brtrue.s   IL_001a
  IL_000f:  nop
  IL_0010:  ldloc.0
  IL_0011:  stloc.2
  IL_0012:  ldloc.0
  IL_0013:  callvirt   instance void ConsoleApplication1.Base::Display()
  IL_0018:  nop
  IL_0019:  nop
  IL_001a:  ret
} // end of method Program::Main

When I look at the documentation it says:

Pushes a null reference (type O) onto the evaluation stack.

for ldnull. Ofcourse, I wasn't expecting to see a source code here, but I'm surprised that there is only a null-check.I thought it may be relevant with compiler optimizations because Derived derives from Base so there is no check the compatibility about the types.then I check out and see that the optimizations are turned off.when I turn on the optimizations there wasn't even null-check.

So the question is why there is nothing generated about is operator ? why I see only a null-check ? Is it somehow relevant with is operator and I couldn't see ?

Selman Genç
  • 100,147
  • 13
  • 119
  • 184
  • I'm about 27% surprised it even did that, considering it already knows that (1) `d` has a value, and (2) any `Derived` is inherently also a `Base` – cHao Jun 07 '14 at 09:06

3 Answers3

17

The type of d is Derived, which is always of type Base or null. That's why the non-optimized code only checks for null.

The optimized code doesn't do a check at all because the optimizer knows that d is not null (since you assigned a new object to it) and didn't change after the assignment.

Philippe Leybaert
  • 168,566
  • 31
  • 210
  • 223
  • Did you check the optimized IL, or is it just something you know? I didn't know the C# compiler optimized like that. Interseting. – Jeppe Stig Nielsen Jun 03 '14 at 19:56
  • That's what any decent optimizer would do. But the OP said that the optimized IL didn't include the null check so the C# optimizer is doing a decent job :-) – Philippe Leybaert Jun 03 '14 at 19:58
  • that makes sense, also explains the optimized code :) on the other hand, this also seems like an optimization , there is no (isintance) call when I use is operator for derived type to check if it is of the base type. – Selman Genç Jun 03 '14 at 20:02
  • @Selman22 The most extreme case is to test a value type (which can't be `null`) for a type which it always is. Try `static void Test(int i) { Console.WriteLine(i is IFormattable); }`. No need to box `i` and test for the interface type since the result is always true. – Jeppe Stig Nielsen Jun 03 '14 at 20:08
  • @JeppeStigNielsen you're right.thanks for the answer by the way,accepted this because I see this first :) – Selman Genç Jun 03 '14 at 20:10
  • The optimized IL includes a `brfalse.s`, which is indeed a null check (essentially `if ( == 0) goto someLine;`). It seems unlikely that the local variable could have been modified between the consecutive `stloc.0` and `ldloc.0` commands, but it *does* allow for this in the strictest sense. – Tim S. Jun 04 '14 at 02:22
8

d has compile-time type Derived, so if d is non-null, it is a Derived and a Derived is always a Base because of the inheritance.

You should not use is in a case like that; it is misleading.

The usual situation with is is the opposite one, where the compile-time type is Base and you check for is Derived.

Jeppe Stig Nielsen
  • 60,409
  • 11
  • 110
  • 181
3

As other have said, this is because the compiler already knows for sure what's happening there. If you wrap eveything in a method and use the is operator in the opposite direction you will see something more convincing:

static void f( Base c ) {
    if ( c is Derived ) {
        Console.WriteLine( "HELLO" );
    }
}

Translates to:

.method private hidebysig static void  f(class test.Base c) cil managed
{
  // Code size       31 (0x1f)
  .maxstack  2
  .locals init ([0] bool CS$4$0000)
  IL_0000:  nop
  IL_0001:  ldarg.0
  IL_0002:  isinst     test.Derived
  IL_0007:  ldnull
  IL_0008:  cgt.un
  IL_000a:  ldc.i4.0
  IL_000b:  ceq
  IL_000d:  stloc.0
  IL_000e:  ldloc.0
  IL_000f:  brtrue.s   IL_001e
  IL_0011:  nop
  IL_0012:  ldstr      "HELLO"
  IL_0017:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_001c:  nop
  IL_001d:  nop
  IL_001e:  ret
} // end of method Program::f
BlackBear
  • 22,411
  • 10
  • 48
  • 86
  • But you switch roles of `Base` and `Derived`. To check for `is Derived` like you do is meaningful. The code from the question checked "the wrong direction". – Jeppe Stig Nielsen Jun 03 '14 at 19:59
  • @BlackBear: or you could use object as the type of the parameter, and then it'd make sense to check for Base as it could be either or soemthing else entirely. – jmoreno Jun 04 '14 at 04:46