An ambiguous call between overloads of two-way implicit cast types when a derived type of one is passed as a parameter

(Trying to find a headline that summarizes the problem can be very difficult!)

I have the following classes with some overloaded methods that cause 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:

  • Commenting on any of the implicit operator methods eliminates the ambiguity.
  • Trying to rename the first overload method to VS will rename the first four calls in the Test () method!

This naturally raises two questions:

  • What kind of compiler error logic is this (i.e. how did the compiler come to ambiguity)?
  • Is there something wrong with this design? Intuitively, there should be no ambiguity, and the breaking call should be resolved the first time the method is overloaded ( OverloadedMethod(MyClass l, MyClass r) ), since MyDerivedClass more closely associated with MyClass , not with custom, but otherwise it doesn't matter MyCastableClass . Moreover, VS refactoring seems to agree with this intuition.

EDIT: After playing with VS refactoring, I saw that VS corresponds to invoking the violation method with the first overload, which is defined in the code, whatever it is. Therefore, if we rearrange two overloads, VS maps the calling call to the MyCastableClass parameter. Questions are still relevant.

+6
source share
2 answers

What is the logic of the compiler error (i.e. how did the compiler come to ambiguity)?

First we need to determine which methods are in the method group. Clearly, there are two methods in a group of methods.

Secondly, we must determine which of these two methods is applicable. That is, each argument is implicitly converted to the corresponding parameter type. Obviously, 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 of which has only one parameter, the rule is that the conversion from argument to parameter type of one should be better than the other.

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

Given the conversion of C1, which is converted from type S to type T1, and the conversion of C2, which is converted from type S to type T2, C1 is a better conversion than C2 if at least one of the following conditions is true: / p>

β€’ Identity conversion exists from S to T1, but not from S to T2

β€’ T1 is a better conversion target than T2

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

β€’ There is an implicit conversion from T1 to T2 and there is no implicit conversion from T2 to T1

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 a banana is more specific than Fruit.

β€’ T1 is a signed integral type, and T2 is an unsigned integral type.

Run the list. Is there an identity conversion from MyDerivedClass to MyCastableClass or MyClass ? No. Is there an implicit conversion from MyClass to MyCastableClass , but not an implicit conversion goes the other way? No. There is no reason to believe that any type is more specific than the other. Are integer types? No.

Therefore, there is no reason to decide that one is better than the other, and therefore it is ambiguous.

Is there something wrong with this design?

The question answers itself. You have found one of the problems.

Intuitively, there should be no ambiguity, and the breaking call should be resolved the first time the method is overloaded, since MyDerivedClass is more closely related to MyClass

Although this may be intuitive for you, the specification does not distinguish in this case between a custom transform and any other implicit transform. However, I note that your distinction is indeed taken into account in some rare cases; see my custom conversion chain article for more details. ( http://blogs.msdn.com/b/ericlippert/archive/2007/04/16/chained-user-defined-explicit-conversions-in-c.aspx )

+8
source

What is this compiler error?

Well, the compiler defines a signature based on a few things. The number and type of parameters are next to the name, one of the most important. The compiler checks to see if the method call is ambiguous. It uses not only the actual type of the parameter, but also types to which it can be implicitly dropped (note that explicit casts are outside the image, they are not used here).

This gives a description of the problem.

Is there something wrong with this design?

Yes. Ambiguous methods are the source of many problems. Especially when using variable types like dynamic . Even in this case, the compiler cannot choose the method of invocation, and this is bad. We want the software to be deterministic, and with this code it cannot be.

You did not ask for this, but I think the best option:

  • Rethink your design. Do you really need implicit castings? If so, why do you need two methods instead of one?
  • Use explicit casting instead of implicit casting to make casting an intentional choice that the compiler can understand.
+3
source

Source: https://habr.com/ru/post/977332/


All Articles