4

在 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,则会出现编译错误。假设我将以下内容添加到 Main() 并尝试编译:

A a = new A();
B b = ((B)a);

我会得到以下信息:

' TestConversionOverloading.A '转换
' TestConversionOverloading.B '

那么为什么不直接从定义中给出错误呢?有没有办法使用任何一种转换?

4

4 回答 4

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);
于 2011-02-23T00:48:53.100 回答
2

根据规范,这是预期的行为。

大量压缩原始文本,这就是在这种情况下发生的情况:编译器将在两个类定义中找到所有可以将 A 转换为 B 的运算符。这将征募A operator B(A a)B operator B(A a). 然后,

如果不存在这样的运算符,或者存在多个这样的运算符,则转换不明确并发生编译时错误。

那么为什么不直接从定义中给出错误呢?因为这两个定义都可以,但正是它们的使用导致了问题的出现。

有没有办法使用任何一种转换?我没有看到一个简单的方法来做到这一点。我正在考虑绕过编译器,手动发出 IL。这样,我认为您可以指示程序使用一个运算符或另一个。不过,不确定这是否完全可行。像 Reflector 这样的工具可以提供帮助。

虽然使用基于运算符的转换有一些好处,但其中一个类将丢失一个运算符,或者您可以更改为基于构造函数的转换或更简单的ToA(A a)and语法FromA(A a)。或者,也许 Eric Lippert 可以用一些语言技巧启发我们!

于 2011-02-23T00:16:32.587 回答
1

查看每个“公共静态隐式运算符 B(A a)”代码行生成的 IL 代码:

.method public hidebysig specialname static
class TestConversionOverloading.B  op_Explicit(class TestConversionOverloading.A a) cil managed

所以这是第一个问题的答案:隐式/显式转换运算符是语法糖。在 MSIL 中,它们看起来像通常的方法(并且确实如此)。当两个不同的类具有相同签名的方法时,没有任何犯罪行为,因为它没有违反任何东西。尽管在这种情况下无法编译转换运算符调用。正如前面提到的,您可以使用反射来获取任一方法的 MethodInfo。

于 2011-09-16T12:41:20.463 回答
0

请记住,冲突转换之一可能是泛型的,并且可能对泛型参数的其他组合有用。

您甚至可以在 SAME 类中定义冲突的转换:

class C<T>
{
    implicit operator int() { return 0; }
    implicit operator T() { return default(T); }
}

C<int> c;
int i = c;

如果编译器对此抱怨,您C<string>将无法同时转换为stringand int

于 2011-02-23T01:42:25.183 回答