CS0121 Ambiguous overloaded function call with char, int, double parameters implicitly converted to user-defined types in C#

224 views Asked by At

I've got some C# classes, MyChar, Myint, MyDouble, that wrap char, int and double. Each has an implicit conversion operator from the wrapped type to the user-defined one. I've also got a set of overloaded functions, ToString(MyChar), ToString(MyInt), ToString(MyDouble).

I want to call ToString(MyChar) by passing in a literal char value, e.g. 'A'. But the compilation fails with CS0121, "The call is ambiguous between the following methods or properties: 'ToString(MyChar)' and 'ToString(MyInt)'". And I get a similar problem if I pass in an int value.

    public class MyChar
    {
        private MyChar(char val) => Value = val;
        public static implicit operator MyChar(char val) => new MyChar(val);
        public char Value { get; }
    }
    public class MyInt
    {
        private MyInt(int val) => Value = val;
        public static implicit operator MyInt(int val) => new MyInt(val);
        public int Value { get; }
    }
    public class MyDouble
    {
        private MyDouble(double val) => Value = val;
        public static implicit operator MyDouble(double val) => new MyDouble(val);
        public double Value { get; }
    }

    public class ConversionTests
    {
        public void DoIt()
        {
            Console.WriteLine(ToString('A')); // CS0121
            Console.WriteLine(ToString(1)); // CS0121
        }

        private static string ToString(MyChar c) => $"{c.Value}";
        private static string ToString(MyInt i) => $"{i.Value}";
        private static string ToString(MyDouble d) => $"{d.Value}";
    }

I've found I can make the compiler accept the int value correctly, by adding an implicit conversion from MyInt to MyDouble

    public class MyDouble
    {
        private MyDouble(double val) => Value = val;
        public static implicit operator MyDouble(double val) => new MyDouble(val);
        public static implicit operator MyDouble(MyInt val) => new MyDouble(val.Value);
        public double Value { get; }
    }
...
        public void DoIt()
        {
            Console.WriteLine(ToString('A')); // CS0121
            Console.WriteLine(ToString(1)); // now compiles :-)
        }

I guess this works, because the resolution mechanism now thinks the conversion to MyDouble is now via the route 1 -> MyInt -> MyDouble, and this can't happen implicitly since it requires two user-defined conversions. In the same vein, I can get ToString('A') to resolve correctly, by adding two more implicit conversions: MyChar to MyDouble, and MyChar to MyInt.

This is fine, since it is mirroring the implicit conversions that take place between char, int and double. But can anyone explain to me why the original code posted above won't compile without the additional conversions, in a way a simple brain like mine might understand?

1

There are 1 answers

0
Panagiotis Kanavos On

A char can be implicitly cast to an int. An int can be implicitly cast to double. Given the implicit casts to the My types, this means that ToString('A') can be handled by both ToString methods.

You could remove all ToString() methods except ToString(MyInt) and the code would still work:

    public class ConversionTests
    {
        public void DoIt()
        {
            Console.WriteLine(ToString('A')); 
            Console.WriteLine(ToString(1));
        }

        private static string ToString(MyInt i) => $"{i.Value}";
    }

This would print :

65
1