4

If you compile the following code with Visual Studio 2010:

    public struct A
    {
        public static implicit operator B(A a)
        {
            Console.WriteLine("11111111111");
            return new B();
        }
    }
    public struct B
    { }
    public static B F(A? a)
    {
        return (B)a;
    }

using ILSpy, return (B)a; is actually compiled as return A.op_Implicit(a.value).

By my understanding of C# 4.0 chapter 6.4.5 'User-defined explicit conversions', it should produce a compiler error.

But, reading ECMA 334 chapter 13.4.4 'User-defined explicit conversions', it has a different rule which the above code seems to comply with.

C# 4.0:

Find the set of applicable user-defined and lifted conversion operators, U. This set consists of the user-defined and lifted implicit or explicit conversion operators declared by the classes or structs in D that convert from a type encompassing or encompassed by S to a type encompassing or encompassed by T. If U is empty, the conversion is undefined and a compile-time error occurs.

ECMA 334:

Find the set of applicable conversion operators, U. This set consists of the user-defined and, if S and T are both nullable, lifted implicit or explicit conversion operators (§13.7.3) declared by the classes or structs in D that convert from a type encompassing or encompassed by S to a type encompassing or encompassed by T. If U is empty, there is no conversion, and a compile-time error occurs.

Am I correct that VS2010 does not comply with the "Evaluation of user-defined conversions" section in the C# 4.0 spec, but does comply with the ECMA spec?

4

1 回答 1

5

让我们先看看当我们遵循各种规则时会发生什么。

遵循 C# 4.0 规范中的规则:

  • 用于搜索用户定义转换的类型集 D 由 A 和 B 组成。
  • 适用转换的集合 U 包括从 A 到 B 的用户定义的隐式转换,以及从 A? 提升的用户定义的隐式转换。给 B?。
  • 我们现在必须选择 U 的这两个元素中唯一最好的一个。
  • 最具体的源类型是 A?。
  • 最具体的目标类型是 B。
  • U 不包含来自 A 的任何转换?到 B,所以这是模棱两可的。

这应该是有道理的。不知道这里的转换是否应该使用提升转换,从A转换?给 B? 然后从 B? 到B,或者我们是否应该使用未提升的转换,从A转换?到 A,然后 A 到 B。


在旁边:

经过更深入的思考,尚不清楚这是一个产生任何差异的差异。

假设我们使用提升的转换。如果一个?是非空的,那么我们将从 A 转换?到A,然后A到B,然后B到B?,然后B?回到B,这将成功。如果一个?为 null 那么我们将从 A 转换吗?直接到一个空 B?,然后在将其解包到 B 时崩溃。

假设我们使用未提升的转换和 A? 是非空的。然后我们从A转换?到A,A到B,完成。如果一个?为 null 那么我们在展开 A 时会崩溃?对 A。

所以在这种情况下,两个版本的转换具有完全相同的动作,所以我们选择哪个并不重要,因此称其为模棱两可是不幸的。但是,这并没有改变编译器显然没有遵循 C# 4 规范的事实。


ECMA 规范呢?

  • 集合 U 包含从 A 到 B 的用户定义转换,但不是提升的转换,因为 S(它是 A?)和 T(它是 B)不是都可以为空的。

现在我们只有一个可供选择,所以重载决议很容易做到。

但是,这并不意味着编译器遵循 ECMA 规范的规则。 事实上,它没有遵循任何规范的规则。更接近ECMA 规范,因为它不会将两个运算符都添加到候选集中,因此,在这种简单的情况下,选择候选集中的唯一成员。但实际上它从未将提升的运算符添加到候选集中,即使源和目标都是可为空的值类型。此外,它在许多其他方面违反了 ECMA 规范,更复杂的示例会显示这些方面:

  • 提升的转换语义(即,在调用方法之前插入一个空检查,如果操作数为空则跳过它)允许用户定义的从非可空结构类型到可空结构类型、指针类型或引用类型的转换!也就是说,如果你有一个从 A 到字符串的转换,那么你会从 A 得到一个提升的转换?如果操作数为空,则生成空字符串的字符串。在这两个规范中都找不到此规则。

  • 根据规范,必须包含或相互包含的类型是被转换的表达式的类型(在规范中称为S)和用户定义的转换的形参类型。C# 编译器实际上检查是否包含正在转换的表达式的基础类型,如果它是可空值类型。(规范中的 S0。)这意味着应该拒绝的某些转换被接受。

  • 根据规范,最佳目标类型应通过查看各种转换的输出类型集来确定,提升或未提升。为了找到最佳输出类型,用户定义的从 A 到 B 的转换应被视为具有 B 输出类型。但是如果你有一个从 A 到 B 的演员表呢?那么编译器实际上会考虑B?作为未提升转换的输出类型,用于确定最具体的输出类型!

关于用户定义的转换处理中的这些和许多其他错误,我可以继续(并且继续......)几个小时。我们在这里几乎没有触及表面;我们甚至还没有研究涉及泛型时会发生什么。但我会放过你。这里的要点是:您不能狭隘地解析任何版本的 C# 规范,并从中确定在复杂的用户定义转换场景中会发生什么。编译器通常会按照用户的期望做,而且通常是出于错误的原因。

这既是规范中最复杂的部分之一,也是编译器最不遵守的规范部分,这是一个糟糕的组合。这是非常不幸的。

我勇敢地尝试让 Roslyn 符合规范,但我失败了;这样做引入了太多现实世界的重大变化。相反,我让 Roslyn 复制了原始编译器的行为,只是使用了更清晰、更易于理解的实现。

于 2013-08-28T15:26:43.980 回答