“as”关键字如何在内部工作?

时间:2009-06-05 11:08:16

标签: c# clr

我知道这个关键字的功能,但我想知道它在较低级别的工作原理。

哪一个更快?他们总能产生相同的结果吗?如果他们这样做,为什么有两种不同的方式?

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

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

Class123 obj = null;

if (someobject is Class123)
{
    obj = (Class123)someobject;
}

5 个答案:

答案 0 :(得分:22)

根据MSDN: as (C# Reference)

  

as运算符就像一个强制转换操作。但是,如果无法进行转换,则返回null而不是引发异常。请考虑以下表达式:

expression as type
  

除了表达式仅被计算一次之外,它等效于以下表达式。

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

第一个变体(作为操作数)...

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

...编译到这个IL代码:

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 

...和第二个变体(是操作数+演员)......

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

...编译到这个IL代码:

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 

...所以您看到唯一的区别是第castclass行中的其他L_0038代码。

答案 1 :(得分:16)

使用as关键字时,没有内部尝试捕获。据我所知,该功能内置于编译器/ CLR中,因此类型检查是隐式和自动的。

简单规则
总是期望对象具有已知类型时使用直接强制转换(如果 错误类型的,则会收到有用的错误)。当对象始终属于已知类型时,请使用as关键字。

as关键字存在的原因纯粹是为了程序员的方便(尽管你建议尝试捕获会更慢)。你可以手动实现它,就像你指出的那样:

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

这突出了'as'关键字主要用于简洁目的的事实。

现在,两者之间的性能差异可能微不足道。由于类型检查,as关键字可能稍微慢一些,但在绝大多数情况下这不太可能影响代码。如上所述,过早优化从来都不是明智之举。基准如果你真的希望,但我会建议只使用哪种方法更方便/适合你的情况,而不是担心性能(或以后,如果你绝对必须)。

答案 2 :(得分:9)

直截了当地设定一些事项:

当您确定对象属于您要转换的类型时,应该进行类型转换。它可以为null(在这种情况下,将返回null,除非它是您要转换为的值类型)

如果您不确定,可以使用“as”运算符。当对象不可转换,或者对象为null时,将返回 null

“as”运算符转换为专用的IL语句( isinst ),而类型转换转换为 castclass IL语句,因此它内置于运行时。编译器只是发出正确的IL语句。

答案 3 :(得分:5)

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

答案 4 :(得分:5)

这个问题已经得到了很好的回答,但到目前为止它已经缺少了难以理解的数字。

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

这些数字一直以这些比例回归

我想指出,从这些数字中得出的唯一结论是,从绩效的角度来看,通过选择其中一种方法而非另一种几乎无法获得。单个呼叫的差异很小(非常小的趋势为零)。也就是说,“as”更快:)

在此之后,上述数字大多是有道理的。

“as”失败的时间比成功时长。成功没有任何反应,可以按原样使用,也可以简单地复制。失败时,需要跳转才能复制空引用。

“失败”会在失败时更快,一次调用“是”并且它不再执行任何操作。成功时它会慢得多,它会调用“是”,然后是演员。

然而,我很惊讶Cast失败的时间比AS失败要长

修改

根据要求,在try / catch块中投射的数据

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

产生第一组数字的代码

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