为什么是“是”?实施为“as”?
鉴于这是一个非常自然的用例(如果您不知道 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 的责任。
此外,与空检查一样,它比两种替代方案(is
和 as
以及 is
和 is
具有更少(且更便宜)的指令。代码>强制转换)。
附录:
带有 nullcheck 的 CIL (.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 和强制转换的 CIL (.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
is 和 as 的 CIL (.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 和调用) () 已删除)。
Given that this is a very natural use case (if you don't know what as
actually does),
if (x is Bar) {
Bar y = x as Bar;
something();
}
is effectively equivalent (that is, the compiler-generated CIL from the above code will be equivalent) to:
Bar y = x as Bar;
if (y != null) {
y = x as Bar; //The conversion is done twice!
something();
}
EDIT:
I guess I hadn't made my question clear. I wouldn't ever write the second snippet as it's of course redundant. I'm claiming that the CIL generated by the compiler when compiling the first snippet is equivalent to the second snippet, which is redundant. Questions: a) Is this correct? b) If so, why is is
implemented like that?
This is because I find the first snippet a lot clearer and prettier than the actually well-written
Bar y = x as Bar;
if (y != null) {
something();
}
CONCLUSION:
Optimizing the is
/as
case is not the compiler's responsibility, but the JIT's.
Also, as with a null check it has fewer (and less expensive) instructions than both of the alternatives (is
and as
and is
and cast
).
Addendum:
CIL for as with 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 and 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
These have been edited for shortness (method declarations, nops and calls to something() removed).
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(10)
您不会执行第二个
y = x as Bar;
,因为您已经有了y,即Bar。You won't do a second
y = x as Bar;
, because your already have y which is Bar.您现在可以编写代码,
我想说的是,代码更清晰一些,但如果没有编译器的真正魔力,速度就不会那么快。
You could write the code now as
That I would say was a bit clearer, but not as fast without real magic from the compiler.
根据博客文章 有多少Passes? 由 Eric Lippert 编写,这是编译器通过。引用:
因此,也许这就是为什么您会看到为两个片段生成相同的 CIL。
According to the blog post How Many Passes? by Eric Lippert that is a compiler pass. To quote:
So perhaps that is why you are seeing the same CIL generated for both snippets.
如果将声明放在循环内,则“y”的范围会减少。
不管是谁写的,可能更喜欢将“x”转换为“T”而不是“(T)x”,并且希望限制“y”的范围。
The scope of 'y' is reduce if you place the declaration inside the loop.
Whoever wrote it probably prefers casting 'x as T' more than '(T)x', and wanted to limit the scope of 'y'.
您忘记了值类型。例如:
不会编译,因为值类型不能像这样转换。
You forgot about value types. Eg:
Won't compile as value types can't be converted like this.
我强烈怀疑 is 比 as 更快并且不需要分配。因此,如果 x 很少是 Bar,那么第一个片段就很好。如果 x 主要是 Bar,则建议使用 as,因为不需要第二次转换。这取决于代码的用法和情况。
I suspect strongly that is is faster than as and does not require an allocation. So if x is rarely ever Bar, then the first snippet is good. If x is mostly Bar, then an as would be recommended, as no second cast is required. It depends on the usage and circumstances of the code.
?是的,尽管我会以另一种方式表述。您是说“is”是 as-followed-by-null 检查的语法糖。我会用另一种方式说:“as”是“检查类型实现,如果成功则强制转换,如果失败则为 null”的语法糖。
也就是说,我更倾向于说
is 实际上等同于
看,你想用“is”来定义“as”,而不是相反。问题实际上应该是“为什么要这样实施?” :-)
因为这是规范的正确实现。
我想我在这里没有遵循你的思路。该实施有什么问题吗?您希望如何实施?您可以使用“isinst”和“castclass”指令;描述您希望看到的程序的代码生成器。
Yes, though I would have stated it the other way. You are saying that "is" is a syntactic sugar for as-followed-by-null-check. I would have said it the other way: that "as" is a syntactic sugar for "check for type implementation, cast if success, null if failure".
That is to say, I would be more inclined to say
is effectively equivalent to
See, you want to define "as" in terms of "is", not the other way around. The question really should be "why is as implemented as is?" :-)
Because that's a correct implementation of the specification.
I think I'm not following your line of thought here. Is there something wrong with that implementation? How would you prefer it to be implemented? You have the "isinst" and "castclass" instructions at your disposal; describe the codegen for your program that you'd like to see.
那么,可用的 IL 指令 (isinst) 将返回适当类型的对象,如果无法进行此类转换,则返回 null。如果无法进行转换,它也不会抛出异常。
鉴于此,“is”和“as”的实现都很简单。在这种情况下,我不会声称“is”被实现为“as”,只是底层 IL 指令允许两者发生。现在,为什么编译器无法将“is”后跟“as”优化为单个 isinst 调用,这是另一回事了。也许,在这种情况下,它与变量作用域有关(即使到了IL的时候,作用域并不真正存在)
编辑
再考虑一下,你不能优化“is”后跟“as”进入单个 isinst 调用,而不知道正在讨论的变量不会受到其他线程更新的影响。
假设x是一个字符串:
这里,y应该为空。
Well, the IL instruction that is available (isinst) will return either an object of the appropriate type, or null if such a conversion is not possible. And it doesn't throw an exception if the conversion isn't possible.
Given that, both "is" and "as" are trivial to implement. I wouldn't claim that "is" is implemented as "as" in this case, just that the underlying IL instruction allows both to occur. Now, why the compiler isn't able to optimize the "is" followed by "as" into a single isinst call, that's another matter. Probably, in this case, it's related to variable scope (even though by the time this is IL, scope doesn't really exist)
Edit
On second thoughts, you can't optimise "is" followed by "as" into a single isinst call, without knowing that the variable under discussion isn't subject to update from other threads.
Assuming x is a string:
Here, y should be null.
在您的示例中,使用
as
无论如何都是多余的。由于您已经知道x 是 Bar
,因此您应该使用强制转换:或者,使用
as
进行转换并仅检查 null:In your example, the use of
as
is redundant anyway. Since you already know thatx is Bar
, you should be using a cast:Alternatively, convert using
as
and just check for null:首先,我不同意你的前提,即这是更典型的用例。这可能是您最喜欢的方法,但惯用的方法是“as + null check”样式:
正如您所发现的,“is”方法需要额外的“as”或强制转换,这就是为什么“as”带有 null 检查根据我的经验,这是执行此操作的标准方法。
我认为这种“as”方法没有任何冒犯性,我个人认为它并不比任何其他代码更令人不愉快。
至于您的实际问题,为什么
is
关键字是根据as
关键字实现的,我不知道,但我确实喜欢您问题中的文字游戏: )我怀疑两者实际上都没有按照另一个实现,但是您用来从 IL 生成 C# 的工具(我猜)将 IL 解释为as
。Firstly I disagree with your premise that this is more typical use case. It may be your favourite approach, but the idiomatic approach is the "as + null check" style:
As you have found the "is" approach requires the extra "as" or a cast, which is why the "as" with null check is the standard way of doing this in my experience.
I see nothing offensive about this "as" approach, personally I don't think it any more unpleasant on the eye than any other code.
As to your actual question, why is the
is
keyword implemented in terms of theas
keyword, I have no idea, but I do like the play on words in your question:) I suspect neither is actually implemented in terms of the other, but the tool (Reflector I guess) you used to generate C# from the IL interpreted the IL in terms ofas
.