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

我知道这个关键字的function,但我想知道它是如何在较低级别上工作的。

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

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

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

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

as关键字存在的原因纯粹是为了程序员的方便(尽管你建议try-catch会更慢)。 你可以手动实现它,如你所指出的那样:

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

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

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

根据MSDN:as(C#Reference) :

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

 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行中的额外的castclass代码。

直截了当地说几件事:

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

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

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

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

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

 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”更快:)

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

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

失败时“强制转换”速度更快,一次呼叫“是”,它不再发生任何事情。 成功时它会慢得多,它会调用“是”,然后是演员阵容。

然而,我很惊讶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 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++; } }