28

鉴于这是一个非常自然的用例(如果您不知道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/ ascase 不是编译器的职责,而是 JIT 的职责。

is此外,与空检查一样,它比两种替代方法( andasisand )具有更少(且成本更低)的指令cast

附录:

CIL 与 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

用于 is 和 cast (.NET 3.5) 的 CIL:

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

用于 is 和 as (.NET 3.5) 的 CIL:

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 和对某事()的调用)。

4

10 回答 10

12

a) 这是正确的吗

是的,尽管我会以另一种方式陈述。您是说“is”是 as-followed-by-null-check 的语法糖。我会以另一种方式说:“as”是“检查类型实现,如果成功则强制转换,如果失败则为空”的语法糖。

也就是说,我更倾向于说

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

等效于

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

看,你想用“is”来定义“as”,而不是相反。真正的问题应该是“为什么要按原样执行?” :-)

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

因为这是规范的正确实现

我想我在这里没有遵循您的思路。该实现有问题吗?您希望如何实施?您可以使用“isinst”和“castclass”指令;描述您希望看到的程序的代码生成器。

于 2010-02-12T16:22:38.800 回答
9

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

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

编辑

再想一想,您不能将“is”后跟“as”优化为单个 isinst 调用,而不知道所讨论的变量不会受到其他线程的更新。

假设 x 是一个字符串:

//Thread1
if(x is string)

//Thread2
x = new ComplexObject();

//Thread1
    y = x as string

在这里,y 应该为空。

于 2010-02-12T11:59:25.120 回答
5

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

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

或者,使用转换as并检查是否为空:

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

}
于 2010-02-12T11:14:07.717 回答
5

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

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

正如您发现的那样,“is”方法需要额外的“as”或强制转换,这就是为什么带有空检查的“as”是根据我的经验执行此操作的标准方法。

我认为这种“as”方法没有任何冒犯之处,我个人认为它不会比任何其他代码更令人不快。

至于您的实际问题,为什么关键字是根据is关键字实现的as,我不知道,但我确实喜欢您问题中的文字游戏:)我怀疑两者都没有真正实现,但是该工具(我猜想反射器)你曾经从 IL 生成 C#,用as.

于 2010-02-12T11:35:21.680 回答
2

你不会做第二个y = x as Bar;,因为你已经有了 y ,它是 Bar。

于 2010-02-12T11:09:52.890 回答
1

根据博客文章多少次通过?由 Eric Lippert 编写,这是一个编译器通行证。去引用:

然后我们运行一个优化过程,重写琐碎的“is”和“as”运算符。

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

于 2010-02-12T12:54:35.860 回答
1

您现在可以将代码编写为

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

我想说的是更清楚一点,但如果没有编译器的真正魔法,速度就没有那么快了。

于 2010-02-12T15:03:51.857 回答
0

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

写它的人可能更喜欢将 'x as T' 转换为 '(T)x',并希望限制 'y' 的范围。

于 2010-02-12T15:06:48.777 回答
0

您忘记了值类型。例如:

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

    }

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

不会编译,因为值类型不能像这样转换。

于 2010-02-12T15:34:50.647 回答
-1

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

于 2010-02-12T12:48:11.217 回答