Welcome to Atalasoft Community Sign in | Help

Is, As, and Casting

C# has two very convenient operators for runtime type checking: is and as.

is takes a class an object and a type and if the object is an instance of the type, is evaluates to true; false otherwise.

as takes an object and a type and if the object is an instance type, as successfully casts the object to the type.

casting blindly takes an object and a type and blindly tries to treat the object as the type.  If it fails, it will throw an InvalidCastException.

Now consider the following three methods:

static string AsAsIsString(object o)
{
    return o is string ? (string)o : null;
}
static string AsString(object o)
{
    return o as string;
}
static string CastString(object o)
{
    return (string)o;
}

All of them are more or less equivalent (CastString is the oddball since it could throw).  The question is, what happens under the hood?  What is the real difference when this code is compiled?

AsAsIsString() is semantically identical to as.  It is as implemented in terms of is.  The question is whether or not it goes beyond semantic and functional equivalence and is actually identical.  Here, I will use the IL for each as the final arbiter:

AsString:
    L_0000: ldarg.0 
    L_0001: isinst string
    L_0006: ret 
AsAsIsString:
    L_0000: ldarg.0 
    L_0001: isinst string
    L_0006: brtrue.s L_000a
    L_0008: ldnull 
    L_0009: ret 
    L_000a: ldarg.0 
    L_000b: castclass string
    L_0010: ret 
CastString:
    L_0000: ldarg.0 
    L_0001: castclass string
    L_0006: ret 

That’s quite a difference.  AsString is pure simplicity – isinst takes the top of the stack and leaves either the object or null if the object is the type.  E-Z.

AsAsIsString does much more work.  Even with optimization turned on, the compiler doesn’t spot the redundant work.  Assuming that castclass and isinst are approximately the same amount of work, this code will take twice as long as AsString.

Finally, we see that the CastString is pretty much the same as AsString.

So the lesson here is that as will be more efficient than the is/cast pair.  Although this is a simple case, you might not spot this inefficiency if you wrote code using this pattern:

public void DoSomethingFabulous(object o)
{
    if (o is IfNode) {
        ((IfNode)o).GenerateTestAndBranch();
    }
    else if (o is WhiteNode) {
        ((WhileNode)o).GenerateTestAndLoop();
    }
    else if (o is RepeatNode) {
        ((RepeatNode)o).GenerateLoopAndTest();
   }
}
public void DoSomethingFabulousMoreEfficiently(object o)
{
    if ((IfNode ifNode = o as IfNode) != null) {
        ifNode.GenerateTestAndBranch();
    }
    else if ((WhileNode whileNode = o as WhileNode) != null) {
        whileNode.GenerateTestAndLoop();
    }
    else if ((RepeatNode repeatNode = o as RepeatNode) != null) {
        repeatNode.GenerateLoopAndTest();
    }
}

 

Ignoring the bad object design here, you can see that the DoSomethingFabulous will have exactly the same problem as AsAsIsString – redundant class checking.  DoSomethingFabulousMoreEfficienctly solves that by making as do double duty – it is both a test and an exceptionless cast.

In general, I think it is probably a good idea to expunge is from your code except where doing so would absolutely destroy readability.  While this might be considered to be preemptive optimizing, as the performance difference will probably only be noticeable in a large scale usage of this (like say an interpreter for a dynamic language), it’s probably a good idea to keep this in your head – is/cast is 2x costly as as.

Published Friday, January 30, 2009 12:13 PM by Steve Hawley

Comments

Wednesday, March 18, 2009 10:28 AM by Steve's Tech Talk

# Make It Feel Like Syntax

I chose my career wisely.  I can tell because at least once a week I run across a nifty small thing

Anonymous comments are disabled