为什么当从 int 到 Foo 存在显式用户定义运算符时,编译器会隐式将双精度转换为 int

本文关键字:int 编译器 转换 双精度 定义 Foo 存在 为什么 用户 运算符 | 更新日期: 2023-09-27 18:36:41

为什么可以从double显式转换为Foo,即使Foo只定义了从intFoo的显式转换?

为什么在我的情况下double隐式转换为int

using System;
class Program
{
    static void Main(string[] args)
    {
        double doub = 15.7;
        Foo foo = (Foo)doub;
        Console.WriteLine(foo.value); //writes "15"
    }
}
struct Foo
{
    public int value;
    public static explicit operator Foo(int val) //no matter if implicit
    {
        return new Foo { value = val };
    }
}

为什么当从 int 到 Foo 存在显式用户定义运算符时,编译器会隐式将双精度转换为 int

我相信

这在 C# 6.4.3 语言规范的第 5 节中有所阐述。

第一个提示在第 6.2.8 节用户定义的显式转换中给出(强调我的):

用户定义的显式转换由可选的标准显式转换组成,然后

执行用户定义的隐式或显式转换运算符,然后是另一个可选的标准显式转换

请注意,不是

一次转换,而是三次转换。

现在,要知道什么是"标准显式转换",我们必须查看第 6.2.3 Standard explicit conversions 节:

标准显式转换

都是标准隐式转换加上显式转换的子集 存在相反标准隐式转换的转换。换句话说,如果一个标准隐式 存在从类型 A 到类型 B 的转换,然后存在从类型 A 到 B 类型的标准显式转换,并且 从B型到A型。

回顾第 6.3.1 Standard implicit conversions 节,我们可以看到这是其中的一部分:

  • 隐式数值转换 (§6.1.2)

换句话说:显式数字转换(如double -> int)可以在用户定义的转换之前应用。

如果我们现在看6.4.3 Evaluation of user-defined conversions,我们会看到以下内容(强调我的):

首先,如果需要,执行从源类型到用户定义或提升转换运算符的操作数类型的标准转换

接下来,调用用户定义或提升的转换运算符来执行转换。

最后,如果需要,从用户定义或提升的结果类型执行标准转换 将运算符转换为目标类型。

这正是您的方案中发生的情况。

C# 规范的相关部分是

  • 6.4.4 用户定义的显式转换

本节很长,但它提炼为:您正在使用 (Foo)doub 定义显式转换,并且由于您使用的是显式转换,编译器将选择最具体的可用转换,包括仅显式转换,以沿着转换路径进行中间步骤(如果可用)。

如果 U 只包含一个用户定义的转换运算符, 从SX转换为TX,那么这是最具体的转换 算子。如果不存在此类运算符,或者如果有多个此类运算符 运算符存在,则转换不明确且编译时 发生错误。否则,将应用用户定义的转换:

如果 S 不是 SX

,则执行从 S 到 SX 的标准显式转换。
调用最具体的用户定义转换运算符以从 SX 转换为 TX。
如果 TX 不是 T,则执行从 TX 到 T 的标准显式转换。

(强调我的)

这里S是源类型,T是目标类型,而SX是在目标类型的显式运算符中定义的最具体的源类型(即; Foo ),TX 是在 T 的显式运算符中定义的最具体的目标类型。

这里存在从doubleint的显式转换,因此选择从源参数类型(double -- S)转换为显式运算符(int -- SX)的输入参数类型是最合适的。 它不需要明确,因为您已经明确了转换为 Foo .

这之所以有效,是因为没有模棱两可的替代方案。 如果您将Foo定义为:

struct Foo
{
    public int value;
    public uint uvalue;
    public static explicit operator Foo(int val)
    {
        return new Foo { value = val };
    }

    public static explicit operator Foo(uint val)
    {
        return new Foo { uvalue = val };
    }
}

例如,这将产生编译时错误(具有相同的Main):

错误 1 从"double"转换为"Foo"时,用户定义的转换"Foo.explicit 运算符 Foo(uint)"和"Foo.explicit 运算符 Foo(int)"不明确

同样,这是本书(根据上面引用的第一段),因为可用的转换集现在U包含两个同样有效的显式转换,并且编译器无法决定您是打算调用int转换还是uint转换。 在最初的示例中,只有一个选择,并且很清楚,因此编译器采用它。