SYSK 116: Which Code is Faster and Why – Using ‘As’ or ‘Is’ + cast?

Which code segment below is faster?

#1

MyClass classInstance = new MyClass();

          

IDoWork iDoWorkClass = classInstance as IDoWork;

if (iDoWorkClass != null)

{

    // Do something so compiler doesn't think it's dead code

    System.Diagnostics.Debug.WriteLine("testing");

}

 

#2

MyClass classInstance = new MyClass();

if (classInstance is IDoWork)

{

    IDoWork iDoWorkClassViaCast = (IDoWork)classInstance;

    // Do same thing as above so we compare apples to apples

    System.Diagnostics.Debug.WriteLine("testing");

}

 

If you answered ‘neither, they are approximately the same’ – you would be right based on my tests and analysis.

 

To understand why, look at the IL that’s produced (I added comments in italic to ease the reading of the IL code below):

 

IL for the first code snippet (using ‘as’):

// Code size 31 (0x1f)

// declare the maximal number of variables we plan to have on the stack at any given time

  .maxstack 2

// declare variables and assign initial values to them

  .locals init ([0] class AsIsComparison.MyClass classInstance,

           [1] class AsIsComparison.IDoWork iDoWorkClass,

           [2] bool CS$4$0000)

// NOP (no operation) instructions in the assembly code are needed to align labels to specific boundaries. This makes instruction-fetch operations more efficient for some processors

  IL_0000: nop

// Create new object of type AsIsComparison.MyClass

  IL_0001: newobj instance void AsIsComparison.MyClass::.ctor()

// store the current stack value in local variable 0, i.e. classInstance (see declaration above)

  IL_0006: stloc.0

// load variable 0 onto stack

  IL_0007: ldloc.0

// store the current stack value in local variable 1 -- iDoWorkClass

  IL_0008: stloc.1

// load variable 1 onto stack

  IL_0009: ldloc.1

// Push a null reference onto the evaluation stack

  IL_000a: ldnull

// Compare two values. If they are equal, the integer value 1 (int32) is pushed onto the evaluation stack; otherwise 0 (int32) is pushed onto the evaluation stack.

  IL_000b: ceq

// store the current stack value in local variable 2 -- (bool CS$4$0000, which is a compiler created variable)

  IL_000d: stloc.2

// load variable 2 onto stack

  IL_000e: ldloc.2

// Transfers control to a target instruction (at address IL_001e) if value is true, not null, or non-zero.

  IL_000f: brtrue.s IL_001e

// Fill space if opcodes are patched. No meaningful operation is performed although a processing cycle can be consumed.

  IL_0011: nop

// load the string constant onto the stack

  IL_0012: ldstr "testing"

// Invoke method

  IL_0017: call void [System]System.Diagnostics.Debug::WriteLine(string)

// No-op

  IL_001c: nop

// No-op

  IL_001d: nop

// Return

  IL_001e: ret

 

IL for the second code snippet (using ‘is’ + cast):

// Code size 31 (0x1f)

// declare the maximal number of variables we plan to have on the stack at any given time

  .maxstack 2

// declare variables and assign initial values to them

  .locals init ([0] class AsIsComparison.MyClass classInstance,

           [1] class AsIsComparison.IDoWork iDoWorkClassViaCast,

           [2] bool CS$4$0000)

// NOP (no operation) instructions in the assembly code are needed to align labels to specific boundaries. This makes instruction-fetch operations more efficient for some processors

  IL_0000: nop

// Create new object of type AsIsComparison.MyClass

  IL_0001: newobj instance void AsIsComparison.MyClass::.ctor()

// store the current stack value in local variable 0, i.e. classInstance (see declaration above)

  IL_0006: stloc.0

// load variable 0 onto stack

  IL_0007: ldloc.0

// Push a null reference onto the evaluation stack

  IL_0008: ldnull

// Compare two values. If they are equal, the integer value 1 (int32) is pushed onto the evaluation stack; otherwise 0 (int32) is pushed onto the evaluation stack.

  IL_0009: ceq

// store the current stack value in local variable 2 -- (bool CS$4$0000, which is a compiler created variable)

  IL_000b: stloc.2

// load variable 2 onto stack

  IL_000c: ldloc.2

// Transfers control to a target instruction (at address IL_001e) if value is true, not null, or non-zero.

  IL_000d: brtrue.s IL_001e

// Fill space if opcodes are patched. No meaningful operation is performed although a processing cycle can be consumed.

  IL_000f: nop

// load variable 0 (classInstance) onto stack

  IL_0010: ldloc.0

// store the current stack value in local variable 1 (iDoWorkClassViaCast)

  IL_0011: stloc.1

// load the string constant onto the stack

  IL_0012: ldstr "testing"

// Invoke method

  IL_0017: call void [System]System.Diagnostics.Debug::WriteLine(string)

// No-op

  IL_001c: nop

// No-op

  IL_001d: nop

// Return

  IL_001e: ret

The code size is the same…  The number of operations is the same…  The operations themselves are the same (the order varies in a couple of places)…  That’s why the performance is the same!