Update!
See my dissection of a portion of the C# spec below; I think I must be missing something, because to me it looks like the behavior I'm describing in this question actually violates the spec.
Update 2!
OK, upon further reflection, and based on some comments, I think I now understand what's going on. The words "source type" in the spec refer to the type being converted from -- i.e., Type2
in my example below -- which simply means that the compiler is able to narrow the candidates down to the two operators defined (since Type2
is the source type for both). However, it cannot narrow the choices any further. So the key words in the spec (as it applies to this question) are "source type", which I previously misinterpreted (I think) to mean "declaring type."
Original Question
Say I have these types defined:
class Type0
{
public string Value { get; private set; }
public Type0(string value)
{
Value = value;
}
}
class Type1 : Type0
{
public Type1(string value) : base(value) { }
public static implicit operator Type1(Type2 other)
{
return new Type1("Converted using Type1's operator.");
}
}
class Type2 : Type0
{
public Type2(string value) : base(value) { }
public static implicit operator Type1(Type2 other)
{
return new Type1("Converted using Type2's operator.");
}
}
Then say I do this:
Type2 t2 = new Type2("B");
Type1 t1 = t2;
Obviously this is ambiguous, as it is not clear which implicit
operator should be used. My question is -- since I cannot see any way to resolve this ambiguity (it isn't like I can perform some explicit cast to clarify which version I want), and yet the class definitions above do compile -- why would the compiler allow those matching implicit
operators at all?
Dissection
OK, I'm going to step through the excerpt of the C# spec quoted by Hans Passant in an attempt to make sense of this.
Find the set of types, D, from which user-defined conversion operators will be considered. This set consists of S (if S is a class or struct), the base classes of S (if S is a class), and T (if T is a class or struct).
We're converting from Type2
(S) to Type1
(T). So it seems that here D would include all three types in the example: Type0
(because it is a base class of S), Type1
(T) and Type2
(S).
Find the set of applicable user-defined conversion operators, U. This set consists of the user-defined implicit conversion operators declared by the classes or structs in D that convert from a type encompassing S to a type encompassed by T. If U is empty, the conversion is undefined and a compile-time error occurs.
All right, we've got two operators satisfying these conditions. The version declared in Type1
meets the requirements because Type1
is in D and it converts from Type2
(which obviously encompasses S) to Type1
(which is obviously encompassed by T). The version in Type2
also meets the requirements for exactly the same reasons. So U includes both of these operators.
Lastly, with respect to finding the most specific "source type" SX of the operators in U:
If any of the operators in U convert from S, then SX is S.
Now, both operators in U convert from S -- so this tells me that SX is S.
Doesn't this mean that the Type2
version should be used?
But wait! I'm confused!
Couldn't I have only defined Type1
's version of the operator, in which case, the only remaining candidate would be Type1
's version, and yet according to the spec SX would be Type2
? This seems like a possible scenario in which the spec mandates something impossible (namely, that the conversion declared in Type2
should be used when in fact it does not exist).