C# 当一个派生类型作为参数传递时,双向隐式可转换类型的重载之间的调用不明确

C# 当一个派生类型作为参数传递时,双向隐式可转换类型的重载之间的调用不明确,c#,overloading,ambiguous,implicit-conversion,C#,Overloading,Ambiguous,Implicit Conversion,(试图找到一个总结问题的标题可能是一项非常艰巨的任务!) 我有以下类,其中包含一些重载方法,这些方法会产生调用模糊编译器错误: public class MyClass { public static void OverloadedMethod(MyClass l) { } public static void OverloadedMethod(MyCastableClass l) { } //Try commenting this out separately fro

(试图找到一个总结问题的标题可能是一项非常艰巨的任务!)

我有以下类,其中包含一些重载方法,这些方法会产生调用模糊编译器错误:

public class MyClass
{
    public static void OverloadedMethod(MyClass l) { }
    public static void OverloadedMethod(MyCastableClass l) { }

    //Try commenting this out separately from the next implicit operator. 
    //Comment out the resulting offending casts in Test() as well.
    public static implicit operator MyCastableClass(MyClass l)
    {
        return new MyCastableClass();
    }

    //Try commenting this out separately from the previous implicit operator.
    //Comment out the resulting offending casts in Test() as well.
    public static implicit operator MyClass(MyCastableClass l)
    {
        return new MyClass();
    }

    static void Test()
    {
        MyDerivedClass derived = new MyDerivedClass();
        MyClass class1 = new MyClass();
        MyClass class2 = new MyDerivedClass();
        MyClass class3 = new MyCastableClass();
        MyCastableClass castableClass1 = new MyCastableClass();
        MyCastableClass castableClass2 = new MyClass();
        MyCastableClass castableClass3 = new MyDerivedClass();

        OverloadedMethod(derived); //Ambiguous call between OverloadedMethod(MyClass l) and OverloadedMethod(MyCastableClass l)
        OverloadedMethod(class1);
        OverloadedMethod(class2);
        OverloadedMethod(class3);
        OverloadedMethod(castableClass1);
        OverloadedMethod(castableClass2);
        OverloadedMethod(castableClass3);

    }

public class MyDerivedClass : MyClass {  }

public class MyCastableClass { }
有两件非常有趣的事情需要注意:

  • 注释掉任何隐式运算符方法都会消除歧义
  • 尝试重命名VS中的第一个方法重载将重命名Test()方法中的前四个调用 这自然会提出两个问题:

  • 编译器错误背后的逻辑是什么(即编译器是如何产生歧义的)
  • 这个设计有什么问题吗?直观地说,应该没有歧义,并且应该在第一个方法重载(
    OverloadedMethod(MyClass l,MyClass r)
    )中解决有问题的调用,因为
    MyDerivedClass
    MyClass
    的关系更为密切,而不是可浇铸的,但在其他方面与
    MyCastableClass
    无关。此外,VS重构似乎符合这种直觉
  • 编辑: 在玩了VS重构之后,我看到VS将有问题的方法调用与在代码中定义的第一个重载相匹配,无论是哪个重载。因此,如果我们交换这两个重载,VS将有问题的调用与带有
    MyCastableClass
    参数的调用相匹配。不过,这些问题仍然有效

    编译器错误背后的逻辑是什么

    嗯,编译器根据一些事情来确定签名。参数的数量和类型在名称旁边,这是最重要的参数之一。编译器检查方法调用是否不明确。它不仅使用参数的实际类型,而且还使用它可以隐式强制转换到的类型(请注意,显式强制转换是不存在的,这里不使用)

    这就是你所描述的问题

    这个设计有什么问题吗

    对。模棱两可的方法是许多问题的根源。尤其是在使用变量类型时,如
    动态
    。即使在这种情况下,编译器也无法选择调用哪个方法,这很糟糕。我们希望软件是确定性的,而有了这段代码,它就不可能是确定性的

    你没有要求,但我想最好的选择是:

  • 重新思考你的设计。你真的需要隐式转换吗?如果是这样,为什么需要两种方法而不是一种
  • 使用
    显式
    强制转换而不是
    隐式
    强制转换,使强制转换成为编译器可以理解的经过深思熟虑的选择
  • 编译器错误背后的逻辑是什么(即编译器是如何产生歧义的)

    首先,我们必须确定方法组中有哪些方法。显然,方法组中有两种方法

    其次,我们必须确定这两种方法中哪一种是适用的。也就是说,每个参数都可以隐式转换为相应的参数类型。显然,这两种方法都适用

    第三,鉴于存在不止一种适用的方法,必须确定唯一的最佳方法。如果只有两个方法,每个方法只有一个参数,那么规则是从参数到参数类型的转换必须比到另一个更好

    规范第7.5.3.5节中规定了一种转换比另一种转换更好的规则,为了方便起见,我在此引用该规则:

    给定从类型S转换为类型T1的转换C1,以及从类型S转换为类型T2的转换C2,如果至少满足以下条件之一,则C1比C2是更好的转换:

    •存在从S到T1的身份转换,但不存在从S到T2的身份转换

    •T1是比T2更好的转换目标

    给定两种不同类型的T1和T2,如果至少满足以下条件之一,则T1是比T2更好的转换目标:

    •存在从T1到T2的隐式转换,并且不存在从T2到T1的隐式转换

    此规则的目的是确定哪种类型更具体。如果每个香蕉都是水果,但不是每个水果都是香蕉,那么香蕉比水果更具体

    •T1为有符号积分类型,T2为无符号积分类型

    列出清单。是否存在从
    MyDerivedClass
    MyCastableClass
    MyClass
    的身份转换?否。是否存在从
    MyClass
    MyCastableClass
    的隐式转换,但不是隐式转换?没有。没有理由认为这两种类型都比另一种更具体。是整型还是整型?没有

    因此,没有什么可以作为一个比另一个更好的决定的基础,因此这是不明确的

    这个设计有什么问题吗

    问题本身就解决了。你发现了其中一个问题

    直观地说,应该没有歧义,并且应该在第一个方法重载中解决有问题的调用,因为MyDerivedClass与MyClass的关系更密切


    虽然这对您来说可能是直观的,但在这种情况下,规范没有区分用户定义的转换和任何其他隐式转换。然而,我注意到,在一些罕见的情况下,你的辨别能力确实很重要;有关详细信息,请参阅我的文章。()

    这两个问题都没有答案,但您关于是否需要两个重载的观点很有趣。不幸的是,在我的例子中,隐式强制转换和重载都是有意义的、有效的,而且我认为是必要的,即使你的观点让我更有效地编写重载。为什么你认为我的答案不能回答这些问题?如果
    A=B
    B=A
    ,编译器在为
    A
    B
    签名时应该选择什么