为什么是“是”?实施为“as”?

发布于 2024-08-21 00:49:23 字数 1818 浏览 11 评论 0原文

鉴于这是一个非常自然的用例(如果您不知道 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 以及 isis 具有更少(且更便宜)的指令。代码>强制转换)。

附录:

带有 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

熊抱啵儿 2024-08-28 00:49:24

您不会执行第二个y = x as Bar;,因为您已经有了y,即Bar。

You won't do a second y = x as Bar;, because your already have y which is Bar.

流心雨 2024-08-28 00:49:24

您现在可以编写代码,

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

我想说的是,代码更清晰一些,但如果没有编译器的真正魔力,速度就不会那么快。

You could write the code now as

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

That I would say was a bit clearer, but not as fast without real magic from the compiler.

九歌凝 2024-08-28 00:49:24

根据博客文章 有多少Passes? 由 Eric Lippert 编写,这是编译器通过。引用:

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

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

According to the blog post How Many Passes? by Eric Lippert that is a compiler pass. To quote:

Then we run an optimization pass that
rewrites trivial "is" and "as"
operators.

So perhaps that is why you are seeing the same CIL generated for both snippets.

放血 2024-08-28 00:49:24

如果将声明放在循环内,则“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'.

月亮是我掰弯的 2024-08-28 00:49:24

您忘记了值类型。例如:

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

    }

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

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

You forgot about value types. Eg:

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

    }

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

Won't compile as value types can't be converted like this.

擦肩而过的背影 2024-08-28 00:49:24

我强烈怀疑 isas 更快并且不需要分配。因此,如果 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.

空袭的梦i 2024-08-28 00:49:23

a) 这是正确的

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

也就是说,我更倾向于说

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

is 实际上等同于

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

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

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

因为这是规范的正确实现

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

a) Is this correct

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

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

is effectively equivalent to

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

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?" :-)

b) If so, why is is implemented like that?

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.

时光是把杀猪刀 2024-08-28 00:49:23

那么,可用的 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应该为空。

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:

//Thread1
if(x is string)

//Thread2
x = new ComplexObject();

//Thread1
    y = x as string

Here, y should be null.

只怪假的太真实 2024-08-28 00:49:23

在您的示例中,使用 as 无论如何都是多余的。由于您已经知道 x 是 Bar,因此您应该使用强制转换:

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

或者,使用 as 进行转换并仅检查 null:

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

}

In your example, the use of as is redundant anyway. Since you already know that x is Bar, you should be using a cast:

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

Alternatively, convert using as and just check for null:

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

}
虫児飞 2024-08-28 00:49:23

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

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

正如您所发现的,“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:

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

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 the as 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 of as.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文