Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
508 views
in Technique[技术] by (71.8m points)

c# - Ambiguous call between overloads of two-way implicit castable types when a derived type of one is passed as parameter

(Trying to find a title that sums up a problem can be a very daunting task!)

I have the following classes with some overloaded methods that produce a call ambiguity compiler error:

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 { }

There are two very interesting things to note:

  1. Commenting out any of the implicit operator methods removes the ambiguity.
  2. Trying to rename the first method overload in VS will rename the first four calls in the Test() method!

This naturally poses two questions:

  1. What it the logic behind the compiler error (i.e. how did the compiler arrive to an ambiguity)?
  2. Is there anything wrong with this design? Intuitively there should be no ambiguity and the offending call should be resolved in the first method overload (OverloadedMethod(MyClass l, MyClass r)) as MyDerivedClass is more closely related to MyClass rather than the castable but otherwise irrelevant MyCastableClass. Furthermore VS refactoring seems to agree with this intuition.

EDIT: After playing around with VS refactoring, I saw that VS matches the offending method call with the first overload that is defined in code whichever that is. So if we interchange the two overloads VS matches the offending call to the one with the MyCastableClass parameter. The questions are still valid though.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

What it the logic behind the compiler error (i.e. how did the compiler arrive to an ambiguity)?

First we must determine what methods are in the method group. Clearly there are two methods in the method group.

Second, we must determine which of those two methods are applicable. That is, every argument is convertible implicitly to the corresponding parameter type. Clearly both methods are applicable.

Third, given that there is more than one applicable method, a unique best method must be determined. In the case where there are only two methods each with only one parameter, the rule is that the conversion from the argument to the parameter type of one must be better than to the other.

The rules for what makes one conversion better than another is in section 7.5.3.5 of the specification, which I quote here for your convenience:

Given a conversion C1 that converts from a type S to a type T1, and a conversion C2 that converts from a type S to a type T2, C1 is a better conversion than C2 if at least one of the following holds:

? An identity conversion exists from S to T1 but not from S to T2

? T1 is a better conversion target than T2

Given two different types T1 and T2, T1 is a better conversion target than T2 if at least one of the following holds:

? An implicit conversion from T1 to T2 exists, and no implicit conversion from T2 to T1 exists

The purpose of this rule is to determine which type is more specific. If every Banana is a Fruit but not every Fruit is a Banana, then Banana is more specific than Fruit.

? T1 is a signed integral type and T2 is an unsigned integral type.

Run down the list. Is there an identity conversion from MyDerivedClass to either MyCastableClass or MyClass? No. Is there an implicit conversion from MyClass to MyCastableClass but not an implicit conversion going the other way? No. There is no reason to suppose that either type is more specific than the other. Are either integral types? No.

Therefore there is nothing upon which to base the decision that one is better than the other, and therefore this is ambiguous.

Is there anything wrong with this design?

The question answers itself. You've found one of the problems.

Intuitively there should be no ambiguity and the offending call should be resolved in the first method overload as MyDerivedClass is more closely related to MyClass

Though that might be intuitive to you, the spec does not make a distinction in this case between a user-defined conversion and any other implicit conversion. However I note that your distiction does count in some rare cases; see my article for details. (Chained user-defined explicit conversions in C#)


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...