Search code examples
c#.netas-keyword

Why is 'is' implemented as 'as'?


Given that this is a very natural use case (if you don't know what as actually does),

if (x is Bar) {
   Bar y = x as Bar;
   something();
}

is effectively equivalent (that is, the compiler-generated CIL from the above code will be equivalent) to:

Bar y = x as Bar;
if (y != null) {
    y = x as Bar; //The conversion is done twice!
    something();
}

EDIT:

I guess I hadn't made my question clear. I wouldn't ever write the second snippet as it's of course redundant. I'm claiming that the CIL generated by the compiler when compiling the first snippet is equivalent to the second snippet, which is redundant. Questions: a) Is this correct? b) If so, why is is implemented like that?

This is because I find the first snippet a lot clearer and prettier than the actually well-written

Bar y = x as Bar;
if (y != null) {
   something();
}

CONCLUSION:

Optimizing the is/as case is not the compiler's responsibility, but the JIT's.

Also, as with a null check it has fewer (and less expensive) instructions than both of the alternatives (is and as and is and cast).

Addendum:

CIL for as with nullcheck (.NET 3.5):

L_0001: ldarg.1
L_0002: isinst string
L_0007: stloc.0
L_0008: ldloc.0
L_0009: ldnull
L_000a: ceq
L_000c: stloc.1
L_000d: ldloc.1
L_000e: brtrue.s L_0019
L_0011: ldarg.0
L_0019: ret

CIL for is and cast (.NET 3.5):

L_0001: ldarg.1
L_0002: isinst string
L_0007: ldnull
L_0008: cgt.un
L_000a: ldc.i4.0
L_000b: ceq
L_000d: stloc.1
L_000e: ldloc.1
L_000f: brtrue.s L_0021
L_0012: ldarg.1
L_0013: castclass string
L_0018: stloc.0
L_0019: ldarg.0
L_0021: ret

CIL for is and as (.NET 3.5):

L_0001: ldarg.1
L_0002: isinst string
L_0007: ldnull
L_0008: cgt.un
L_000a: ldc.i4.0
L_000b: ceq
L_000d: stloc.1
L_000e: ldloc.1
L_000f: brtrue.s L_0021
L_0012: ldarg.1
L_0013: isinst string
L_0018: stloc.0
L_0019: ldarg.0
L_0021: ret

These have been edited for shortness (method declarations, nops and calls to something() removed).


Solution

  • a) Is this correct

    Yes, though I would have stated it the other way. You are saying that "is" is a syntactic sugar for as-followed-by-null-check. I would have said it the other way: that "as" is a syntactic sugar for "check for type implementation, cast if success, null if failure".

    That is to say, I would be more inclined to say

    if (x is Bar) { 
       Bar y = x as Bar; 
       something(); 
    } 
    

    is effectively equivalent to

    if (x is Bar) { 
       Bar y = (x is Bar) ? (Bar)x : (Bar) null; 
       something(); 
    } 
    

    See, you want to define "as" in terms of "is", not the other way around. The question really should be "why is as implemented as is?" :-)

    b) If so, why is is implemented like that?

    Because that's a correct implementation of the specification.

    I think I'm not following your line of thought here. Is there something wrong with that implementation? How would you prefer it to be implemented? You have the "isinst" and "castclass" instructions at your disposal; describe the codegen for your program that you'd like to see.