好奇的空合并运算符的自定义隐式转换行为 [英] Curious null-coalescing operator custom implicit conversion behaviour

查看:94
本文介绍了好奇的空合并运算符的自定义隐式转换行为的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

注意:这似乎已被固定在罗斯林

Note: this appears to have been fixed in Roslyn

写我的回答这一,其中谈到了的null-coalescing运营商

This question arose when writing my answer to this one, which talks about the associativity of the null-coalescing operator.

正如一个提醒,空合并运算符的想法是形式的前pression

Just as a reminder, the idea of the null-coalescing operator is that an expression of the form

x ?? y

首先评估 X ,则:


  • 如果值 X 为空,进行评估,这是前任$的最终结果p $ pssion

  • 如果值 X 终止非空,终止的的评估和值 X 是前pression的最终结果,转换到是<的编译时类型后/ code>如果有必要

  • If the value of x is null, y is evaluated and that is the end result of the expression
  • If the value of x is non-null, y is not evaluated, and the value of x is the end result of the expression, after a conversion to the compile-time type of y if necessary

现在一般的没有必要的转换,或者它只是从可空类型为非可空一 - 通常是类型相同,或者刚刚从(说) INT? INT 。然而,你的可以的创建自己的隐式转换运营商,以及那些被用于在必要时。

Now usually there's no need for a conversion, or it's just from a nullable type to a non-nullable one - usually the types are the same, or just from (say) int? to int. However, you can create your own implicit conversion operators, and those are used where necessary.

有关的简单当X?是,我还没有看到任何奇怪的行为。然而,随着(X?Y)? ž我看到一些混乱行为。

For the simple case of x ?? y, I haven't seen any odd behaviour. However, with (x ?? y) ?? z I see some confusing behaviour.

下面是一个简短但完整的测试程序 - 结果是在评论:

Here's a short but complete test program - the results are in the comments:

using System;

public struct A
{
    public static implicit operator B(A input)
    {
        Console.WriteLine("A to B");
        return new B();
    }

    public static implicit operator C(A input)
    {
        Console.WriteLine("A to C");
        return new C();
    }
}

public struct B
{
    public static implicit operator C(B input)
    {
        Console.WriteLine("B to C");
        return new C();
    }
}

public struct C {}

class Test
{
    static void Main()
    {
        A? x = new A();
        B? y = new B();
        C? z = new C();
        C zNotNull = new C();

        Console.WriteLine("First case");
        // This prints
        // A to B
        // A to B
        // B to C
        C? first = (x ?? y) ?? z;

        Console.WriteLine("Second case");
        // This prints
        // A to B
        // B to C
        var tmp = x ?? y;
        C? second = tmp ?? z;

        Console.WriteLine("Third case");
        // This prints
        // A to B
        // B to C
        C? third = (x ?? y) ?? zNotNull;
    }
}

因此​​,我们有三个自定义值类型, A B C ,从一个转换到B,A至C和B到C

So we have three custom value types, A, B and C, with conversions from A to B, A to C, and B to C.

我能理解这两个第二种情况和第三种情况......但是的为什么的是有一个额外的A到B的转换在第一种情况?特别是,我的真正的预料第一种情况和第二种情况是同样的事情 - 它只是提取前pression到一个局部变量,毕竟

I can understand both the second case and the third case... but why is there an extra A to B conversion in the first case? In particular, I'd really have expected the first case and second case to be the same thing - it's just extracting an expression into a local variable, after all.

这是怎么回事赞成吗?我非常hesistant当它涉及到C#编译器哭了错误,但我难倒什么回事...

Any takers on what's going on? I'm extremely hesistant to cry "bug" when it comes to the C# compiler, but I'm stumped as to what's going on...

编辑:好吧,这里是发生了什么事情的厉害例如,由于配置的回答,这给了我更多的理由认为这是一个错误。编辑:样品甚至没有需要两个空凝聚运营商现在...

Okay, here's a nastier example of what's going on, thanks to configurator's answer, which gives me further reason to think it's a bug. The sample doesn't even need two null-coalescing operators now...

using System;

public struct A
{
    public static implicit operator int(A input)
    {
        Console.WriteLine("A to int");
        return 10;
    }
}

class Test
{
    static A? Foo()
    {
        Console.WriteLine("Foo() called");
        return new A();
    }

    static void Main()
    {
        int? y = 10;

        int? result = Foo() ?? y;
    }
}

这个输出是:

Foo() called
Foo() called
A to int

事实上,美孚()被调用两次在这里是非常令人惊讶的我 - 我看不到任何理由为前pression为评估的两倍。

The fact that Foo() gets called twice here is hugely surprising to me - I can't see any reason for the expression to be evaluated twice.

推荐答案

感谢大家谁在分析这个问题作出了贡献。这显然​​是一个编译器错误。它出现时,有对聚结操作者的左手侧涉及两个空类型一个提升转换为仅发生

Thanks to everyone who contributed to analyzing this issue. It is clearly a compiler bug. It appears to only happen when there is a lifted conversion involving two nullable types on the left-hand side of the coalescing operator.

我还没确定了precisely不如意的事情,但在某些时候在编译过程中的可空降阶段 - 初步分析之后,但在code代之前 - 我们降低前pression

I have not yet identified where precisely things go wrong, but at some point during the "nullable lowering" phase of compilation -- after initial analysis but before code generation -- we reduce the expression

result = Foo() ?? y;

从上面的例子中,以道德等价的:

from the example above to the moral equivalent of:

A? temp = Foo();
result = temp.HasValue ? 
    new int?(A.op_implicit(Foo().Value)) : 
    y;

这显然是不正确;正确的降低是

Clearly that is incorrect; the correct lowering is

result = temp.HasValue ? 
    new int?(A.op_implicit(temp.Value)) : 
    y;

根据我的分析,我最好的猜测,到目前为止是可为空的优化是怎么回事出轨。我们有一个为空的优化,看起来对于有些情况下我们知道,可空类型的特定前pression不可能为null。请看下面的天真分析:我们可以先说

My best guess based on my analysis so far is that the nullable optimizer is going off the rails here. We have a nullable optimizer that looks for situations where we know that a particular expression of nullable type cannot possibly be null. Consider the following naive analysis: we might first say that

result = Foo() ?? y;

是相同的

A? temp = Foo();
result = temp.HasValue ? 
    (int?) temp : 
    y;

然后我们可以说,

and then we might say that

conversionResult = (int?) temp 

是相同的

A? temp2 = temp;
conversionResult = temp2.HasValue ? 
    new int?(op_Implicit(temp2.Value)) : 
    (int?) null

不过,优化器可以介入并说:哇,等一下,我们已经检查了临时不为null;没有需要检查它,只是因为我们呼吁一个提升转换运算符空第二次。我们希望他们优化它拿走,只是

But the optimizer can step in and say "whoa, wait a minute, we already checked that temp is not null; there's no need to check it for null a second time just because we are calling a lifted conversion operator". We'd them optimize it away to just

new int?(op_Implicit(temp2.Value)) 

我的猜测是,我们正在某处缓存的事实,(INT?)富()新的诠释?(op_implicit的优化型(富()值))但实际上不是我们想要的优化形式;我们要美孚()的优化形式 - 替代 - 与暂时性和-然后转换

My guess is that we are somewhere caching the fact that the optimized form of (int?)Foo() is new int?(op_implicit(Foo().Value)) but that is not actually the optimized form we want; we want the optimized form of Foo()-replaced-with-temporary-and-then-converted.

许多错误是坏缓存决策的结果。一个聪明人的话:每次缓存一个事实,供以后使用的时候,你可能会创建一个矛盾的东西应该改变相关。在这种情况下已更改后初步分析相关的事情是,作为一个取一个临时的调用美孚()应该总是能够实现。

Many bugs in the C# compiler are a result of bad caching decisions. A word to the wise: every time you cache a fact for use later, you are potentially creating an inconsistency should something relevant change. In this case the relevant thing that has changed post initial analysis is that the call to Foo() should always be realized as a fetch of a temporary.

我们做了很多可空重写通过在C#3.0的重组。该漏洞再现了C#3.0和4.0,但不是在C#2.0,这意味着该错误可能是我不好。对不起!

We did a lot of reorganization of the nullable rewriting pass in C# 3.0. The bug reproduces in C# 3.0 and 4.0 but not in C# 2.0, which means that the bug was probably my bad. Sorry!

我会输入到数据库中的错误,我们会看到,如果我们能得到这个搞掂了语言的未来版本。再次感谢大家的分析;这是非常有帮助的!

I'll get a bug entered into the database and we'll see if we can get this fixed up for a future version of the language. Thanks again everyone for your analysis; it was very helpful!

这篇关于好奇的空合并运算符的自定义隐式转换行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆