等效的内隐操作员:为什么合法?
-
01-10-2019 - |
题
更新!
请参阅下面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
满足要求,因为 Type1
在 d 它转换为 Type2
(显然包括 s) 到 Type1
(显然是由 t)。版本输入 Type2
还 出于完全相同的原因,满足要求。所以 你 包括这两个操作员。
最后,关于找到最具体的“源类型” sx 操作员 你:
如果U转换为S中的任何运算符,则SX为S。
现在, 两个都 运营商在 你 转换 s - 所以这告诉我 sx 是 s.
这是否意味着 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可用。在我的示例中,可能的目标是 Int32
和 UInt32
, ,也不是另一个更好的匹配,所以这就是转换失败的地方。编译器无法知道是否 float f = t
方法 float f = (float)(Int32)t
或者 float f = (float)(UInt32)t
.
- 如果您完全包含一个从SX转换为TX的用户定义的转换操作员,那么这是最特定的转换操作员。如果不存在此类操作员,或者存在一个以上的操作员,则转换是模棱两可的,并且会发生编译时间误差。
在Dan的示例中,我们在这里失败了,因为我们从SX到TX剩下两次转换。如果我们在确定SX和TX时选择不同的转换,则无法从SX到TX进行转换。例如,如果我们有一个 Type1a
来自 Type1
, ,那么我们可能会转换 Type2
到 Type1a
并来自 Type0
到 Type1
这些仍然会给我们SX = type2和tx = type1,但实际上我们没有从Type2到Type1的任何转换。没关系,因为这确实是模棱两可的。编译器不知道是将type2转换为type1a,然后施放为type1,还是首先铸造为type0,以便它可以使用该转换为type1。
其他提示
最终,不能完全成功地禁止它。您和我可以发布两个集会。他们可以在更新自己的同时开始使用彼此的组装。然后,我们每个人都可以在每个组件中定义的类型之间提供隐式铸件。只有当我们发布下一个版本时,才能将其捕获,而不是在编译时。
不试图禁止无法禁止的事情是有优势的,因为这使得它具有清晰和一致性(这是立法者的一堂课)。