为什么'''被实现为'as'?

时间:2010-02-12 11:07:43

标签: c# .net as-keyword

鉴于这是一个非常自然的用例(如果你不知道as实际上做了什么),

if (x is Bar) {
   Bar y = x as Bar;
   something();
}

实际上是等效的(也就是说,编译器生成的CIL来自上面的代码将是等价的):

Bar y = x as Bar;
if (y != null) {
    y = x as Bar; //The conversion is done twice!
    something();
}

修改

我想我没有说清楚我的问题。我不会写第二个片段,因为它当然是多余的。我声称在编译第一个片段时编译器生成的CIL等同于第二个片段,这是多余的。问题:a)这是正确的吗? b)如果是这样,为什么is实现了那样?

这是因为我发现第一个片段比实际编写得更清晰,更漂亮

Bar y = x as Bar;
if (y != null) {
   something();
}

结论:

优化is / as案例不是编译器的责任,而是JIT的责任。

此外,与空检查一样,它具有比两个备选(isas以及iscast)更少(且更便宜)的指令。

附录:

CIL for nullcheck(.NET 3.5):

L_0001: ldarg.1
L_0002: isinst string
L_0007: stloc.0
L_0008: ldloc.0
L_0009: ldnull
L_000a: ceq
L_000c: stloc.1
L_000d: ldloc.1
L_000e: brtrue.s L_0019
L_0011: ldarg.0
L_0019: ret

CIL for is and cast(.NET 3.5):

L_0001: ldarg.1
L_0002: isinst string
L_0007: ldnull
L_0008: cgt.un
L_000a: ldc.i4.0
L_000b: ceq
L_000d: stloc.1
L_000e: ldloc.1
L_000f: brtrue.s L_0021
L_0012: ldarg.1
L_0013: castclass string
L_0018: stloc.0
L_0019: ldarg.0
L_0021: ret

CIL for is和as(.NET 3.5):

L_0001: ldarg.1
L_0002: isinst string
L_0007: ldnull
L_0008: cgt.un
L_000a: ldc.i4.0
L_000b: ceq
L_000d: stloc.1
L_000e: ldloc.1
L_000f: brtrue.s L_0021
L_0012: ldarg.1
L_0013: isinst string
L_0018: stloc.0
L_0019: ldarg.0
L_0021: ret

这些已被编辑为简洁(方法声明,nops和删除的某些东西的调用)。

10 个答案:

答案 0 :(得分:12)

  

a)这是正确的

是的,虽然我会以另一种方式说明。你是说“是”是一个后跟空检查的语法糖。我会用另一种方式说:“as”是“检查类型实现,如果成功则转换,如果失败则为null”的语法糖。

也就是说,我更倾向于说

if (x is Bar) { 
   Bar y = x as Bar; 
   something(); 
} 

实际上等同于

if (x is Bar) { 
   Bar y = (x is Bar) ? (Bar)x : (Bar) null; 
   something(); 
} 

看,你想用“是”来定义“as”,而不是相反。问题应该是“为什么按原样实施?” : - )

  

b)如果是这样,为什么会这样实施?

因为正确实施规范

我想我在这里没有遵循你的想法。这个实现有问题吗?您希望如何实施?您可以使用“isinst”和“castclass”说明;描述您希望看到的程序的codegen。

答案 1 :(得分:9)

嗯,可用的IL指令(isinst)将返回相应类型的对象,如果无法进行此类转换,则返回null。如果转换不可能,它不会抛出异常。

鉴于此,“是”和“作为”都是微不足道的。在这种情况下,我不会声称“is”被实现为“as”,只是底层IL指令允许两者都发生。现在,为什么编译器无法优化“is”后跟“as”进入单个isinst调用,这是另一回事。可能,在这种情况下,它与变量范围有关(即使这是IL,范围实际上并不存在)

修改

再想一想,你不能在单个isinst调用中优化“is”后跟“as”,而不知道正在讨论的变量不受其他线程的更新。

假设x是一个字符串:

//Thread1
if(x is string)

//Thread2
x = new ComplexObject();

//Thread1
    y = x as string

这里,y应为null。

答案 2 :(得分:5)

在您的示例中,as的使用无论如何都是多余的。既然您已经知道x is Bar,那么您应该使用演员:

if (x is Bar)
{
    Bay y = (Bar)x;
}

或者,使用as进行转换,然后检查null:

Bar y = x as Bar;
if (y != null)
{

}

答案 3 :(得分:5)

首先,我不同意你的前提,即这是更典型的用例。它可能是您最喜欢的方法,但惯用法是“as + null check”样式:

Bar y = x as Bar; 
if (y != null) { 
   something(); 
}

正如您所发现的那样,“is”方法需要额外的“as”或强制转换,这就是为什么“as”with null check是根据我的经验这样做的标准方式。

对于这种“as”方法我没有看到任何令人反感的举动,我个人认为它不会比任何其他代码更令人不快。

至于你的实际问题,为什么is关键字是根据as关键字实现的,我不知道,但我确实喜欢你问题中的文字游戏:)我怀疑实际上并没有实际用另一个实现,但是你用来从IL生成C#的工具(Reflector I guess)用as来解释IL。

答案 4 :(得分:2)

你不会再做y = x as Bar;,因为你已经有了吧。

答案 5 :(得分:1)

根据Eric Lippert的博客文章How Many Passes?,这是一个编译器传递。引用:

  

然后我们运行一个优化传递   重写琐碎的“是”和“作为”   运算符。

所以也许这就是为什么你看到为两个片段生成相同的CIL。

答案 6 :(得分:1)

您现在可以将代码编写为

DoIfOfType<Bar>(possibleBar, b => b.something())

我想说的是有点清楚,但没有编译器的真正魔力那么快。

答案 7 :(得分:0)

如果将声明放在循环中,则“y”的范围会减小。

无论是谁编写它都可能更喜欢将'x as T'强制为'(T)x',并希望限制'y'的范围。

答案 8 :(得分:0)

你忘记了价值类型。例如:

    static void Main(string[] args)
    {
        ValueType vt;
        FooClass f = vt as FooClass;

    }

    private class FooClass
    {
        public int Bar { get; set; }
    }

无法编译,因为值类型无法像这样转换。

答案 9 :(得分:-1)

我强烈怀疑 更快,并且不需要分配。因此,如果x很少是Bar,那么第一个片段就是好的。如果x主要是Bar,则建议使用 as ,因为不需要第二次强制转换。这取决于代码的用法和环境。