Let’s first see what should happen when we follow different rules.
Following the rules of the C # 4.0 specification:
- The set of D types for searching for user transformations consists of A and B.
- Does the set U of applicable conversions consist of a custom implicit conversion from A to B and a user-removed implicit conversion from A? to B ?.
- Now we have to choose the only best of these two elements U.
- The most specific type of source is A ?.
- The most specific type of target is B.
- U does not contain any conversion from A? to B, so this is ambiguous.
That should make sense. We do not know here whether the transformation should use the canceled conversion, the conversion from A? to B? and then from B? to B, or should you use the unprocessed conversion, convert from A? to A, and then from A to B.
ASIDE:
With a deeper reflection, it is not clear that this is a difference that has some meaning.
Suppose we used a raised transformation. If a? doesn’t matter null, then we move from A? to A, then from A to B, then B to B ?, then B? back to B that will be successful. If a? is zero, then we get from A? straight to zero B ?, and then crash when unpacking, which is for B.
Suppose we use a non-transition transform and A? irrelevant. Then we convert from A? to A, AB, done. If a? is null, then we crash when deploying A? to A.
Thus, in this case, both versions of the transformation have exactly the same effect, therefore it doesn’t really matter what we choose, and therefore to call it ambiguity is unfortunate. However, this does not alter the fact that the compiler obviously does not match the letter of the C # 4 specification .
What about the ECMA specification?
- The set U consists of a custom conversion from A to B, but not with the conversion canceled, because S (which is A?) And T (which is B) are not null and void.
And now we have only one choice, so overload resolution has an easy job.
However, this does not mean that the compiler complies with the rules of the ECMA specification. In fact, he abides by the rules of non-specification. . It is closer to the ECMA specification because it does not add both operators to the candidate set, and therefore, in this simple case, selects a single candidate member. But in fact, he never adds the captured statement to the set of candidates, even if both sources and targets are NULL value types. In addition, it violates the ECMA specification in many other ways, which will be shown by more complex examples:
The raised semantics of the transformation (that is, inserting a null check before calling the method and skipping it if the operand is null) are allowed in user conversions from a non-nullable structure type to a NULL structure type, pointer type, or reference type! That is, if you have a conversion from A to string, do you get the canceled conversion from A? for a string that creates an empty string if the operand is NULL. This rule is not found in any specification.
According to the specification, the types that should embrace or embrace each other are the type of expression being transformed (called S in the specification) and the formal parameter type of the user transformation. The C # compiler actually checks if the base type of the expression being converted matches if it is a value type with a null value. (S0 in the specification). This means that some conversions that must be rejected are accepted.
According to the specification, the best type of target should be determined by looking at the set of output types of the various transformations, whether or not removed. A custom conversion from A to B should be considered as having output type B to find the best type of output. But if you had a throw from A to B? will the compiler actually consider B? as an output type of non-transitional transformation for the purpose of determining the most specific type of output!
I could go on (and so on ...) for hours about these and many other errors in custom conversion processing. We barely scratched the surface here; we didn’t even get into what happens when generics are involved. But I will spare you. The conclusion is: you cannot narrowly parse any version of the C # specification and determine from it what will happen in a complex custom conversion script. The compiler usually does what the user expects, and usually does it for the wrong reasons.
This is both one of the most difficult parts of the specification, and part of the specification that the compiler executes with the least, which is a bad combination. This is very sad.
I made a glorious attempt to bring Roslyn in line with the specification, but I failed; this is being done, there are too many real changes in the world. Instead, I got Roslyn to copy the behavior of the original compiler, just with a much cleaner and cleaner implementation.