更新!

请参阅下面C#规格的一部分的解剖;我想我一定会缺少一些东西,因为 看来我在这个问题中描述的行为实际上违反了规格。

更新2!

好的,经过进一步的反思,并根据一些评论,我想我现在了解发生了什么。规格中的单词“源类型”是指正在转换的类型 - IE, Type2 在下面的示例中 - 这仅表示编译器能够将候选人缩小到两个运营商的定义(因为 Type2 是两者的源类型)。但是,它不能进一步缩小选择。因此,规格中的关键词(由于适用于这个问题)是 “源类型”, ,我以前误解了(我认为)的意思是“宣布类型”。


原始问题

说我有这些类型的定义:

class Type0
{
    public string Value { get; private set; }

    public Type0(string value)
    {
        Value = value;
    }
}

class Type1 : Type0
{
    public Type1(string value) : base(value) { }

    public static implicit operator Type1(Type2 other)
    {
        return new Type1("Converted using Type1's operator.");
    }
}

class Type2 : Type0
{
    public Type2(string value) : base(value) { }

    public static implicit operator Type1(Type2 other)
    {
        return new Type1("Converted using Type2's operator.");
    }
}

然后说我这样做:

Type2 t2 = new Type2("B");
Type1 t1 = t2;

显然这是模棱两可的,因为尚不清楚哪个 implicit 应使用操作员。我的问题是 - 因为我看不到 任何 解决这种歧义的方法(这不是我可以执行一些明确的演员来阐明我想要哪个版本),但是上面的类定义确实编译了 - 为什么编译器允许那些匹配的编译器 implicit 完全操作员?


解剖

好的,我将介绍Hans Passant引用的C#规格的摘录,以试图理解这一点。

查找将考虑用户定义的转换操作员的类型D类型。该集合由s(如果是类或结构),S的基类(如果是类)和t(如果t是类或结构)组成。

我们正在转换 Type2 (s) Type1 (t)。所以似乎在这里 d 该示例中将包括所有三种类型: Type0 (因为这是 s), Type1 (t) 和 Type2 (s).

找到一组适用的用户定义的转换操作员,U。该组由用户定义的隐式转换操作员组成,该类别或D中的类别或结构从包含s的类型转换为T的类型。 ,转换是未定义的,并且会发生编译时间误差。

好吧,我们有两个运营商满足了这些条件。该版本在 Type1 满足要求,因为 Type1d 它转换为 Type2 (显然包括 s) 到 Type1 (显然是由 t)。版本输入 Type2 出于完全相同的原因,满足要求。所以 包括这两个操作员。

最后,关于找到最具体的“源类型” sx 操作员 :

如果U转换为S中的任何运算符,则SX为S。

现在, 两个都 运营商在 转换 s - 所以这告诉我 sxs.

这是否意味着 Type2 版本应该使用?

可是等等!我很困惑!

我不能 只要 定义 Type1操作员的版本,在这种情况下,剩下的候选人将是 Type1的版本,但根据规格 sx 将会 Type2?这似乎是规格规定不可能的某些情况的可能场景(即,转换在 Type2 实际上不存在时应使用)。

有帮助吗?

解决方案

我们真的不希望它是一个编译时间错误,只是为了定义转换 可能 引起歧义。假设我们更改type0来存储双重,并且由于某种原因,我们要为签名的整数和未签名的整数提供单独的转换。

class Type0
{
    public double Value { get; private set; }

    public Type0(double value)
    {
        Value = value;
    }

    public static implicit operator Int32(Type0 other)
    {
        return (Int32)other.Value;
    }

    public static implicit operator UInt32(Type0 other)
    {
        return (UInt32)Math.Abs(other.Value);
    }

}

这可以很好地编译,我可以使用两种转换

Type0 t = new Type0(0.9);
int i = t;
UInt32 u = t;

但是,尝试这是一个编译错误 float f = t 因为任何一个隐式转换都可以用于进入整数类型,然后可以将其转换为浮点。

我们只希望编译器实际使用时会抱怨这些更复杂的歧义性,因为我们希望上面的0 type 0来编译。为了保持一致性,更简单的歧义还应在您使用的点而不是定义它时会导致错误。

编辑

由于汉斯删除了引用规格的答案,因此,以下是C#规格的快速运行,它决定了转换是否模棱两可,将U定义为所有可能可以完成工作的转换的集合:

  • 在U:找到运算符的最特定源类型:SX:
    • 如果U转换为S中的任何运算符,则SX为S。
    • 否则,SX是U运算符的组合类型类型中最包含的类型。如果找不到大多数包含类型的类型,则转换是模棱两可的,并且会发生编译时误差。

释义,我们更喜欢直接从s转换的转换,否则我们更喜欢转换s的“最简单”的类型。在这两个示例中,我们都有两个可用的转换。如果没有转换 Type2, ,我们希望从 Type0 超过一个 object. 。如果没有一种类型显然是更好的转换选择,那么我们在这里失败了。

  • 在U中找到最特定的目标类型TX:
    • 如果U转换为T中的任何操作员,则TX为T。
    • 否则,TX是U运算符的组合类型类型中最包含的类型。如果找不到大多数包含类型的类型,则转换是模棱两可的,并且会发生编译时误差。

同样,我们希望直接转换为t,但是我们将选择“最简单”转换为T的类型。在Dan的示例中,我们有两次转换为T可用。在我的示例中,可能的目标是 Int32UInt32, ,也不是另一个更好的匹配,所以这就是转换失败的地方。编译器无法知道是否 float f = t 方法 float f = (float)(Int32)t 或者 float f = (float)(UInt32)t.

  • 如果您完全包含一个从SX转换为TX的用户定义的转换操作员,那么这是最特定的转换操作员。如果不存在此类操作员,或者存在一个以上的操作员,则转换是模棱两可的,并且会发生编译时间误差。

在Dan的示例中,我们在这里失败了,因为我们从SX到TX剩下两次转换。如果我们在确定SX和TX时选择不同的转换,则无法从SX到TX进行转换。例如,如果我们有一个 Type1a 来自 Type1, ,那么我们可能会转换 Type2Type1a 并来自 Type0Type1 这些仍然会给我们SX = type2和tx = type1,但实际上我们没有从Type2到Type1的任何转换。没关系,因为这确实是模棱两可的。编译器不知道是将type2转换为type1a,然后施放为type1,还是首先铸造为type0,以便它可以使用该转换为type1。

其他提示

最终,不能完全成功地禁止它。您和我可以发布两个集会。他们可以在更新自己的同时开始使用彼此的组装。然后,我们每个人都可以在每个组件中定义的类型之间提供隐式铸件。只有当我们发布下一个版本时,才能将其捕获,而不是在编译时。

不试图禁止无法禁止的事情是有优势的,因为这使得它具有清晰和一致性(这是立法者的一堂课)。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top