简体   繁体   中英

How does the “as” keyword work internally?

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;
}

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 .

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).

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.

因为可能更快,因为只需要将类型ckeck一次,而+ cast需要检查类型两次。

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++;
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM