Share via


The difference between is and as and dealing with FxCop Warning CA1800: variable is cast multiple times

I had the following code

 public static void f1(object ob)
{
    if (ob is Foo && ((Foo)ob).m == 10)
        Console.WriteLine("Yea");
}

This gave the following FxCop (Code analysis) performance warning: 'ob', a parameter, is cast to type 'ConsoleApplication5.Foo' multiple times in method 'Program.f1(Object):Void'. Cache the result of the 'as' operator or direct cast in order to eliminate the redundant castclass instruction.

I could easily resolve this by using

 public static void f2(object ob)
{
    Foo foo = ob as Foo;
    if (foo != null && foo.m == 10)
        Console.WriteLine("Yea");
}

Or in my scenario I could've ignored this warning as this code handles a very corner scenario where the is is supposed to fail almost 99.99% times and hence the cast bit would almost never be called (hence double cast would happen say 00.01% times).

I had used the is subconsciously feeling that is is lighter weight than as . Even the as docs expresses as to be equivalent to expression is type ? (type)expression : (type)null;

But if that was the case then there should've been no double cast warning (as is doesn't do a cast). So I did the obvious. Pulled up reflector on both these methods.

The generated IL for f1 is as follows

 L_0002: isinst ConsoleApplication5.Foo<br>L_0007: brfalse.s L_001d
L_0009: ldarg.0 
L_000a: castclass ConsoleApplication5.Foo
L_000f: ldfld int32 ConsoleApplication5.Foo::m
L_0014: ldc.i4.s 10
L_0016: ceq 
L_0018: ldc.i4.0 
L_0019: ceq 
L_001b: br.s L_001e
L_001d: ldc.i4.1 
L_001e: stloc.0 
L_001f: ldloc.0 
L_0020: brtrue.s L_002d
L_0022: ldstr "Yea"
L_0027: call void [mscorlib]System.Console::WriteLine(string)
L_002c: nop 
L_002d: ret 

And that for f2 is

 L_0002: isinst ConsoleApplication5.Foo<br>L_0007: stloc.0 <br>L_0008: ldloc.0 <br>L_0009: brfalse.s L_001a
L_000b: ldloc.0 
L_000c: ldfld int32 ConsoleApplication5.Foo::m
L_0011: ldc.i4.s 10
L_0013: ceq 
L_0015: ldc.i4.0 
L_0016: ceq 
L_0018: br.s L_001b
L_001a: ldc.i4.1 
L_001b: stloc.1 
L_001c: ldloc.1 
L_001d: brtrue.s L_002a
L_001f: ldstr "Yea"
L_0024: call void [mscorlib]System.Console::WriteLine(string)
L_0029: nop 
L_002a: ret 

So for both is and as the isinst IL instruction is used. One more interesting thing I noticed in the 2nd case is that after isinst there is no other casting IL used. Some more investigations revealed from this link that "The isinst instruction evaluates whether the reference on the stack is a Foo or a subclass of the Foo type. If it is, then the reference is cast to the type defined in the isinst instruction and pushed onto the stack. Otherwise the value null is pushed onto the stack."

So this means that the documentation was over-simplifying the situation. is does the whole cast bit and the check is done by the brfalse.s L_001d  instruction which just checks the cast returned null or not. So in reality the equivalence is as follows

is = (expression as type) != null;

Comments

  • Anonymous
    June 20, 2007
    I think the main difference then would be that in the case of 'is' the value is merely checked to be not null after it has been casted, where as the 'as' would return the reference to the casted object, which in turn then may be null. So from the 'is' operator no reference is returned, and that makes it a boolean expression, but the 'as' operator actually returns a reference to the casted object. So I'ld prefer to use the as operator, and would also argue that FxCop is correct.

  • Anonymous
    June 20, 2007
    The comment has been removed

  • Anonymous
    June 21, 2007
    very interesting, so use "is" when the only thing you need it for, is to find out if "it is" that type of object, not if you plan to type cast later

  • Anonymous
    June 21, 2007
    This means that this is a terrible pattern, because it does a ton of casts:if (NextMessage is CloseProgramMessage)

    HandleCloseProgram(NextMessage as CloseProgramMessage);
    else if (NextMessage is DataChangedMessage)
    ReloadData(NextMessage as DataChangedMessage);
    else if (NextMessage is DoMyTaxesMessage)
    DoTaxes(NextMessage as DoMyTaxesMessage);
    ...I guess a better way to do this would be:CloseProgramMessage closeProgramMessage = NextMessage as CloseProgramMessage;DataChangeMessage dataChangedMessage = NextMessage as DataChangeMessage;DoMyTaxesMessage doMyTaxesMessage = NextMessage as DoMyTaxesMessage;if (closeProgramMessage != null)
    HandleCloseProgram(closeProgramMessage);
    if (dataChangedMessage != null)
    ReloadData(dataChangedMessage);
    if (doMyTaxesMessage != null)
    DoTaxes(doMyTaxesMessage);
    ...

  • Anonymous
    January 21, 2008
    CloseProgramMessage closeProgramMessage = NextMessage as CloseProgramMessage; DataChangeMessage dataChangedMessage = NextMessage as DataChangeMessage; DoMyTaxesMessage doMyTaxesMessage = NextMessage as DoMyTaxesMessage; if (closeProgramMessage != null) HandleCloseProgram(closeProgramMessage); if (dataChangedMessage != null) ReloadData(dataChangedMessage); if (doMyTaxesMessage != null) DoTaxes(doMyTaxesMessage); ... Using this code is bad idea becouse you spend time for creating link on object (DataChangeMessage dataChangedMessage = NextMessage as DataChangeMessage) every time. So, performance is down in comparison with "is". See IL.

  • Anonymous
    March 09, 2011
    nice explanation i would like to have some simpler examples

  • Anonymous
    March 09, 2011
    nice explanation i would like to have some simpler examples