tags:

views:

454

answers:

5

I know the function of this keyword, but I would like to know how it works on a lower level.

Which one is faster? And do they always yield the same result? If they do, why are there two different ways?

// Is there an overhead? An internal try catch?
Class123 obj = someobject as Class123;

if (Class123 != null)
{
    //OK
}

or

Class123 obj = null;

if (someobject is Class123)
{
    obj = (Class123)someobject;
}
+12  A: 

There's no internal try-catch happening when using the as keyword. The functionality is built in to the compiler/CLR, as far as I know, so the type check is implicit and automated.

Simple rule:
Use a direct cast when you always expect the object to have a known type (and thus receive a helpful error if it is by chance of the wrong type). Use the as keyword when the object is always of a known type.

The reason for the existance of the as keyword is purely for the convenience of the programmer (although you are correct in suggesting that a try-catch would be slower). You could implement it yourself manually as such, as you point out:

var castObj = (obj is NewType) ? (NewType)obj : null;

This highlights the fact that the 'as' keyword is primarily there for the purpose of conciseness.

Now, the performance difference between the two is likely to be negligible. The as keyword is probably marginally slower because of the type check, but this is unlikely to affect code in the vast majority of situations. As oft said, premature optimisation is never a wise thing to be doing. Benchmark if you really wish, but I would advise just to use whichever method is more convenient/appropiate for your situation, and not worry about performance at all (or later if you absolutely must).

Noldorin
I had not thought the null, which is important, but when I say about "try", if the object was not convertible(sorry for bad english)
Fujiy
Surely "as" and a cast behave the same in the presence of a null? Shouldn't it be "use a direct cast when you never expect the object to be of any other type, use 'as' when the object may possibly be of another type"?
Groky
The as might be slightly slower but in if(x is T) (T)x.Foo() the typecheck is needed twice, probably a little slower than x==nul.
Henk Holterman
I'm a little confused by your "simple rule". Whether the object is null or not doesn't have a lot to do with the "is" vs "as" operator. Don't you mean: when you never expect the object to be of a non-derived type? Also, the "as" operator is built into the runtime (CLR), which is not really the compiler.
Philippe Leybaert
Yeah, sorry. I'm not sure what I was thinking there! I've corrected the post anyway, so let me know if you see anything else dodgy.
Noldorin
@activa: It's a feature of the compiler and the CLR, if you want to be precise.
Noldorin
is multiplication also a feature of the compiler? If so, then the "as" operator is indeed a feature of the compiler :)
Philippe Leybaert
@activa: My point was that it's mainly for convenience, and the language keyword offers this. Does the "as" keyword have a unique CIL code itself, or does it just get converted into a type check followed by a cast? If it's the former, then you're correct. I'm inclined to think it's the latter however.
Noldorin
it has its own IL code
Philippe Leybaert
the IL code for "as" is isinst. For a type cast it's castclass
Philippe Leybaert
@activa: You seem to be right. Still, the fact that it's a keyword means the compiler has some sort of role to play. Thanks for clarifying that, however.
Noldorin
I agree with Groky. Obviously, null as AnyType == null. But the point of "as" is to avoid a InvalidCastException, not a NullReferenceException. The answer is still partially incorrect.
Matthew Flaschen
@Matthew: Yeah, I meant to correct my answer on the first edit, but apparently missed a few sentences. Should be fine *now*. :P
Noldorin
+4  A: 

as may be faster because only needs to ckeck the type once while is + cast need to check the type twice.

ggf31416
I'm not sure this is correct. The "as" keyword does exactly the same thing, just implicitly, as far as I'm aware.
Noldorin
This explaination is also given in FxCop as a performance rule. So I guess it's correct.
Scoregraphic
+18  A: 

According to MSDN: as (C# Reference):

The as operator is like a cast operation. However, if the conversion is not possible, as returns null instead of raising an exception. Consider the following expression:

expression as type

It is equivalent to the following expression except that expression is evaluated only one time.

expression is type ? (type)expression : (type)null


The first variant (as operand) ...

string str1 = strAsObject as string;
if (str1 != null)
{
    this.blabla(str1);
}

... compiles to this IL code:

L_0009: ldloc.1 
L_000a: isinst string
L_000f: stloc.2 
L_0010: ldloc.2 
L_0011: ldnull 
L_0012: ceq 
L_0014: stloc.s CS$4$0000
L_0016: ldloc.s CS$4$0000
L_0018: brtrue.s L_0024
L_001a: nop 
L_001b: ldarg.0 
L_001c: ldloc.2 
L_001d: call instance void TestWinFormsApplication001.Form1::blabla(string)
L_0022: nop 
L_0023: nop

... and the second variant (is operand + cast) ...

if (strAsObject is string)
{
    string str2 = (string) strAsObject;
    this.blabla(str2);
}

... compiles to this IL code:

L_0024: ldloc.1 
L_0025: isinst string
L_002a: ldnull 
L_002b: cgt.un 
L_002d: ldc.i4.0 
L_002e: ceq 
L_0030: stloc.s CS$4$0000
L_0032: ldloc.s CS$4$0000
L_0034: brtrue.s L_0047
L_0036: nop 
L_0037: ldloc.1 
L_0038: castclass string
L_003d: stloc.3 
L_003e: ldarg.0 
L_003f: ldloc.3 
L_0040: call instance void TestWinFormsApplication001.Form1::blabla(string)
L_0045: nop 
L_0046: nop

... so you see the only difference is the additional castclass code in line L_0038.

ulrichb
+7  A: 

To set a few things straight:

Type casting should be done when you are sure the object is of the type you're casting to. It can be null (in that case, null will be returned, unless it is a value type you're casting to)

When you are not sure, the "as" operator can be used. When the object is not castable, or the object is null, null will be returned.

The "as" operator translates to a dedicated IL statement (isinst), while a type cast translates to the castclass IL statement, so it is built into the runtime. The compiler simply emits the correct IL statement.

Philippe Leybaert
Yeah, this is indeed right. Up-voted, since you deserve much of the credit for the clarifications here.
Noldorin
I would have edited your answer instead of creating a new one, but I am not allowed to do that (yet) :-)
Philippe Leybaert
+3  A: 

This question has already been answered well, however so far it has been missing hard numbers.

Over 100000000 iterations
AS   : Failure  00:00:00.9282403
Cast : Failure  00:00:00.9868966
AS   : Success  00:00:00.9350227
Cast : Success  00:00:01.1382759

The figures consistently come back in these proportions

I want to point out that the only conclusion to take from these figures is that from a performance point of view, there is very little to be gained by choosing one of these methods over the other. There's a very little in the difference for a single call (where very little tends to zero). That said, "as" is faster :)

After that, the above figures mostly stand to reason.

"As" takes longer on failure than it does on success. On success nothing happens, the value can be used as is, or simply copied. On failure it requires a jump to copy a null reference.

"Cast" is faster on failure, one call to "is" and it doesn't do any more. On success it's much slower, it has the over head of the call to "is" and then the cast.

However I'm surprised that Cast on failure takes longer than AS failure

Edit

As requested, figures for cast in a try / catch block

Over 100000000 iterations
Catch : Failure 05.05:00:00 // approximately, because I didn't hang around
Catch : Success 00:00:01.4000952

The code that produced the first set of figures

class Program
{
    const int ITERATION_COUNT = 100000000;
    private static UInt64 stringCount = 0;
    private static UInt64 objectCount = 0;
    static void Main(string[] args)
    {
        Console.WriteLine("Over {0} iterations ", ITERATION_COUNT);

        string s = "Hello";
        object o = new Int32();

        RunTest("AS   : Failure  {0}", TestAs, o);
        RunTest("Cast : Failure  {0}", TestIs_And_Cast, o);
        RunTest("AS   : Success  {0}", TestAs, s);
        RunTest("Cast : Success  {0}", TestIs_And_Cast, s);

        Console.WriteLine("Press any key to stop");
        Console.ReadKey();

    }
    private static void RunTest(string testDescription, Action<object> testToRun, object arg)
    {
        Stopwatch sw = new Stopwatch();
        sw.Start();
        for (int i = 0; i < ITERATION_COUNT; i++)
            testToRun(arg);
        sw.Stop();
        Console.WriteLine(testDescription, sw.Elapsed);
    }
    static void TestAs(object obj)
    {
        string s = obj as string;
        if (s != null)
            stringCount++;
        else
            objectCount++;
    }
    static void TestIs_And_Cast(object obj)
    {
        string s = null;
        if (obj is string)
        {
            s = (string)obj;
            stringCount++;
        }
        else
            objectCount++;
    }
}
Binary Worrier
"as" is more elegant than "is" followed by a cast. I agree that the performance impact is negligible. Maybe you can also add a test case using cast with exception handling. Hopefully that will make some people realize that catching exceptions for this kind of stuff is "not done". :)
Philippe Leybaert