在C#中,为什么在“相同”的用户定义转换存在两次时没有编译错误? (一次在源类中,一次在目标类中?)
例如,如果我尝试编译以下代码,则不会出现编译错误:
namespace TestConversionOverloading
{
public class A
{
public int value = 1;
public static explicit operator B(A a)
{
B b = new B();
b.value = a.value + 6;
return b;
}
}
public class B
{
public int value = 2;
public static explicit operator B(A a)
{
B newB = new B();
newB.value = a.value;
return newB;
}
}
public class program
{
public static void Main() {}
}
}
但是,如果我尝试将A显式转换为B,那么 do 会出现编译错误。假设我将以下内容添加到Main()并尝试编译:
A a = new A();
B b = ((B)a);
我会得到以下内容:
模糊的用户定义转化次数 'TestConversionOverloading.A.explicit operator TestConversionOverloading.B(TestConversionOverloading.A)'
和 “TestConversionOverloading.B.explicit 操作者 TestConversionOverloading.B(TestConversionOverloading.A)'
从转换为'TestConversionOverloading.A'到'TestConversionOverloading.B'
那么为什么不直接从定义中给出错误?有没有办法使用这两种转换?
答案 0 :(得分:3)
我不会推测为什么语言允许这样做是有意义的,但是如果你控制这两个类,那么显而易见的解决办法是摆脱其中一个运算符。
如果你不能,这是一种消除反思歧义的方法。
首先,创建一个绑定到目标运算符的委托:
// Picks the conversion operator declared in class A.
var method = typeof(A).GetMethod("op_Explicit", new[] { typeof(A) });
var converter = (Func<A, B>)Delegate.CreateDelegate(typeof(Func<A, B>), method);
然后使用委托:
A a = ...
B b = converter(a);
答案 1 :(得分:2)
根据the spec,这是预期的行为。
严重压缩原始文本,这是在这种情况下发生的情况:编译器将找到所有可以在两个类定义中将A转换为B 的运算符。这将招募A operator B(A a)
和B operator B(A a)
。然后,
如果不存在此类运算符,或者如果存在多个此类运算符,则转换不明确并且发生编译时错误。
那么为什么不直接从定义中给出错误?因为这两个定义都没问题,但是它们的使用会导致问题出现。
有没有办法可以使用这两种转换?我没有看到一种简单的方法来执行此操作。我正在考虑绕过编译器,手动发射IL。这样我认为你可以指示程序使用一个操作符或另一个操作符。但不确定这是否完全可行。像Reflector这样的工具可以提供帮助。
虽然使用基于运算符的转换有一些美感,但其中一个类会丢失一个运算符,或者您可以更改为基于构造函数的转换或更简单的ToA(A a)
和FromA(A a)
语法。或许Eric Lippert可以用一些语言聪明来启发我们!
答案 2 :(得分:1)
查看根据“公共静态隐式运算符B(A a)”代码行生成的IL代码:
.method public hidebysig specialname static
class TestConversionOverloading.B op_Explicit(class TestConversionOverloading.A a) cil managed
所以这是第一个问题的答案:隐式/显式转换运算符是语法糖。在MSIL中,它们看起来像通常的方法(它们是)。当两个不同的类具有相同签名的方法时没有任何罪行,因为它没有违反任何内容。虽然在这种情况下无法编译转换运算符调用。 正如提到的那样,您可以使用反射来获取任一方法的MethodInfo。
答案 3 :(得分:0)
请记住,其中一个冲突的转化可能是通用的,并且可能对其他通用参数组合有用。
您甚至可以在SAME类中定义冲突转换:
class C<T>
{
implicit operator int() { return 0; }
implicit operator T() { return default(T); }
}
C<int> c;
int i = c;
如果编译器抱怨这一点,您将失去C<string>
转换为string
和int
的能力。