4

In C#, why are there no compilation errors when the "same" user-defined conversion exists twice? (once in the source class and once in the target class?)

For example, if I try to compile the following code I get no compilation errors:

namespace TestConversionOverloading
{
    public class A
    {
        public int value = 1;

        public static explicit operator B(A a)
        {
            B b = new B();

            b.value = a.value + 6;

            return b;
        }
    }

    public class B
    {
        public int value = 2;

        public static explicit operator B(A a)
        {
            B newB = new B();

            newB.value = a.value;

            return newB;
        }
    }

    public class program
    {
        public static void Main() {}
    }
}

However, if I try to explicitly convert A to B, I do get a compilation error. Say I add the following to Main() and try to compile:

A a = new A();
B b = ((B)a);

I'll get the following:

Ambiguous user defined conversions 'TestConversionOverloading.A.explicit operator TestConversionOverloading.B(TestConversionOverloading.A)'
and 'TestConversionOverloading.B.explicit operator TestConversionOverloading.B(TestConversionOverloading.A)'
when converting from 'TestConversionOverloading.A' to 'TestConversionOverloading.B'

So why not give an error straight from definition? Could there be a way to use either conversion?

  • If you're in control of both classes, remove it from one of them. If you're only in control of one of them, remove it from that. Since it is easy to detect the problem (though it appears to be at the wrong point), it probably does not make sense to spend a lot of time by the compiler team on adding this check. – Lasse V. Karlsen Feb 23 '11 at 10:09
  • @Lasse: What if I control none of them? Also, the compiler team already bothered about checking for ambiguity when the conversion is called, so it makes me think it's not about the effort of making this check, but about keeping the possibilities of using it opened (like the one in Ani's answer regarding reflection). Or perhaps, because it's a part of a bigger process of calling user-defined conversion, this ambiguity check got in there along the way (plus what you said about it, being easy to detect so no need to bother checking the definitions) – Shmuel Valariola Feb 23 '11 at 11:37
  • I have found a similar question : [Equivalent implicit operators: why are they legal?](http://stackoverflow.com/questions/3561619/equivalent-implicit-operators-why-are-they-legal). However, the question there was settled with: "because those conversions _might_ (and therefore _might not_) cause ambiguity" with a more expanded explanation of how the process broken down. Though There's also a mention of disambiguating with reflection in the comments - but too bad it didn't turn as an "answer" so it would be harder to miss. – Shmuel Valariola Feb 23 '11 at 13:43

4 Answers4

3

I wouldn't speculate on why it makes sense for the language to allow this, but if you are in control of both classes, the obvious solution is to get rid of one of the operators.

If you can't, here's a way to disambiguate using reflection.

First, create a delegate that binds to the intended operator:

// Picks the conversion operator declared in class A.
var method = typeof(A).GetMethod("op_Explicit", new[] { typeof(A) });
var converter = (Func<A, B>)Delegate.CreateDelegate(typeof(Func<A, B>), method);

And then use the delegate as:

A a = ...
B b = converter(a);
Ani
  • 111,048
  • 26
  • 262
  • 307
  • Thanks, it could be that because any of them can be used using reflection is why the compiler didn't error about it. – Shmuel Valariola Feb 23 '11 at 09:49
  • @Shmuel : Possibly, but it has no problems disallowing other 'smells' that could be converted to legal CIL. – Ani Feb 23 '11 at 09:54
  • But I wonder if there could be more reasons to allow it? Or maybe a more broader reason? I may need to know more about the thinking behind the development of C#, or behind Visual Studio's C# compiler. – Shmuel Valariola Feb 23 '11 at 09:59
  • @Shmuel: I honestly don't have a guess. Perhaps if Eric Lippert turned up.. – Ani Feb 23 '11 at 10:01
  • @Ani: can you please elaborate on what you mean by other 'smells'? – Shmuel Valariola Feb 23 '11 at 10:05
  • @Shmuel: E.g. defining parameterless c'tors for value-types, overloads that differ by return-type only. – Ani Feb 23 '11 at 10:11
  • I've decided to mark this as accepted as it is closer to what I was looking for. Though I'd be gratful to figure out whether that's all there is to it or are there more situations involved. **Also**, I encourage checking the other two answers (by Humberto and Ben) and the first comment in the question, as they add useful insight on this situation. Perhaps I should edit my question and add a summary? – Shmuel Valariola Feb 23 '11 at 13:50
2

According to the spec, this is the expected behavior.

Heavily compressing the original text, here's what happens in this case: the compiler will find all operators that could convert A to B in both class definitions. This would enlist A operator B(A a) and B operator B(A a). Then,

If no such operator exists, or if more than one such operator exists, then the conversion is ambiguous and a compile-time error occurs.

So why not give an error straight from definition? Because both definitions are OK, but it's their use that makes the problem arise.

Could there be a way to use either conversion? I don't see an easy way to do this. I'm thinking of bypassing the compiler, emitting IL by hand. That way I think you can instruct the program to use one operator or the other. Not sure if this is entirely feasible, though. A tool like Reflector could help.

While there's some beauty using operator-based conversions, either one of the classes will lose one operator, or you could change to constructor-based conversions or a more straightforward syntax of ToA(A a) and FromA(A a). Or maybe Eric Lippert could enlighten us with some language cleverness!

Humberto
  • 7,117
  • 4
  • 31
  • 46
  • Thnaks. But if both definitions are OK, then (assuming this is intended) it probably means there is some legitimate way to use either as originally written. Otherwise how can it be considered OK? (Sure, by specification they're OK. But, why is it OK spec-wise?) – Shmuel Valariola Feb 23 '11 at 10:00
1

Look at the IL code generated per "public static implicit operator B(A a)" code line:

.method public hidebysig specialname static
class TestConversionOverloading.B  op_Explicit(class TestConversionOverloading.A a) cil managed

So here is the answer on the first question: Implicit/explicit convertion operators are syntactic sugar. In MSIL they are look like usual methods (and they are). There is nothing criminal when two different classes have methods with identical signature as it does not violate anything. Though conversion operator call cannot be compiled in this case. And as it was mentioned you can use reflection to get MethodInfo of either method.

AlexACD
  • 11
  • 1
0

Remember that one of the conflicting conversions could be generic, and could be useful for other combinations of generic parameters.

You can even have conflicting conversions defined in the SAME class:

class C<T>
{
    implicit operator int() { return 0; }
    implicit operator T() { return default(T); }
}

C<int> c;
int i = c;

If the compiler complained about this, you'd lose the ability for C<string> to convert to both string and int.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • Thanks, I can see the point for generic classes. But I don't see how this explains why it should be allowed in non generic classes (or can't be caught in definition for non generic classes) . – Shmuel Valariola Feb 23 '11 at 09:47