C# 等价隐式运算符:为什么合法? 更新!
请参见下面我对C#spec部分的剖析;我想我一定是遗漏了什么,因为对我来说,我在这个问题中描述的行为实际上违反了规范 更新2! 好的,在进一步思考之后,根据一些评论,我想我现在明白发生了什么。规范中的“源类型”一词指的是从转换而来的类型,即我下面的示例中的C# 等价隐式运算符:为什么合法? 更新!,c#,.net,implicit,ambiguity,implicit-conversion,C#,.net,Implicit,Ambiguity,Implicit Conversion,请参见下面我对C#spec部分的剖析;我想我一定是遗漏了什么,因为对我来说,我在这个问题中描述的行为实际上违反了规范 更新2! 好的,在进一步思考之后,根据一些评论,我想我现在明白发生了什么。规范中的“源类型”一词指的是从转换而来的类型,即我下面的示例中的Type2,这意味着编译器能够将候选对象缩小到定义的两个运算符(因为Type2是这两个运算符的源类型)。然而,它不能进一步缩小选择范围。因此,规范中的关键词(适用于这个问题)是“源类型”,我以前(我认为)将其误解为“声明类型” 原始问题 假设
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;
显然,这是不明确的,因为不清楚应该使用哪个隐式
运算符。我的问题是——因为我看不到任何解决这种歧义的方法(我不可能执行一些显式转换来澄清我想要的版本),但是上面的类定义是编译的——为什么编译器会允许那些匹配的隐式操作符?
解剖 好的,我将仔细阅读Hans Passant引用的C#spec的摘录,试图理解这一点 找到类型集D,从中 用户定义的转换运算符将 被考虑。这一套包括 (如果S是类或结构),则基 S的类(如果S是一个类)和T (如果T是类或结构) 我们正在从
Type2
(S)转换为Type1
(T)。因此,这里的D似乎将包括示例中的所有三种类型:Type0
(因为它是S的基类)、Type1
(T)和Type2
(S)
找到一组适用的
用户定义的转换运算符,U。
此集合由用户定义的
声明的隐式转换运算符
通过D中的类或结构
从包含S的类型转换为
由T包围的类型。如果U为
空,则转换未定义且
发生编译时错误
好的,我们有两个算子满足这些条件。Type1
中声明的版本符合要求,因为Type1
位于D中,并且它从Type2
(显然包含S)转换为Type1
(显然包含在T中)。由于完全相同的原因,Type2
中的版本也满足要求。因此U包括这两个操作符
最后,关于在U中查找运算符最具体的“源类型”SX:
如果U中的任何运算符从S转换,则SX是S。
现在,U中的两个操作符都从S转换而来,这告诉我SX是S
这不是意味着应该使用Type2
版本吗?
但是等等!我糊涂了
难道我不能只定义
Type1
的操作符版本吗?在这种情况下,剩下的唯一候选将是Type1
的版本,而根据规范SX将是Type2
?这似乎是一种可能的情况,在这种情况下,规范规定了一些不可能的事情(即,在Type2
中声明的转换实际上不存在时,应该使用该转换)。最终,它不能被完全成功地禁止。你和我可以发布两个程序集。我们可以开始使用彼此的汇编,同时更新自己的汇编。然后,我们可以在每个程序集中定义的类型之间提供隐式强制转换。只有在我们发布下一个版本时,才能捕捉到这一点,而不是在编译时
不试图禁止不能被禁止的事情有一个好处,因为这有助于清晰和一致性(这对立法者来说是一个教训)。我们不希望仅仅为了定义可能导致歧义的转换而将其视为编译时错误。假设我们将Type0更改为存储一个double,出于某种原因,我们希望提供对有符号整数和无符号整数的单独转换
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
是一个编译错误,因为任何一个隐式转换都可以用于获取整数类型,然后可以将其转换为float
我们只希望编译器在实际使用时抱怨这些更复杂的歧义,因为我们希望编译上面的Type0。为了保持一致性,更简单的歧义也应该在使用它时而不是在定义它时导致错误
编辑
由于Hans删除了引用规范的答案,下面是C#规范中确定转换是否不明确的部分的快速运行,其中定义U为可能完成该工作的所有转换的集合:
- 查找U中运算符的最具体源类型SX:
- 如果U中的任何操作符从S转换,那么SX就是S
- 否则,SX是U中运算符的目标类型组合集中包含最多的类型。如果找不到包含最多的类型,则转换不明确,并发生编译时错误