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 removedAnonymous
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 laterAnonymous
June 21, 2007
This means that this is a terrible pattern, because it does a ton of casts:if (NextMessage is CloseProgramMessage)
else if (NextMessage is DataChangedMessage)HandleCloseProgram(NextMessage as CloseProgramMessage);
else if (NextMessage is DoMyTaxesMessage)ReloadData(NextMessage as DataChangedMessage);
...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)DoTaxes(NextMessage as DoMyTaxesMessage);
if (dataChangedMessage != null)HandleCloseProgram(closeProgramMessage);
if (doMyTaxesMessage != null)ReloadData(dataChangedMessage);
...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 examplesAnonymous
March 09, 2011
nice explanation i would like to have some simpler examples