1

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?

bonio
  • 21
  • 3
  • 1
    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 – Panagiotis Kanavos Oct 20 '21 at 11:20
  • What are you trying to do? Are you trying things out or is this a simplified case of a real problem? – Panagiotis Kanavos Oct 20 '21 at 11:26
  • @PanagiotisKanavos It's the simplest reduction of a problem I found in my code. I want to present a complete API, so I don't have the luxury of removing methods. I want to use an overloaded name, as in the user's mind the operation is the same thing, irrespective of the type its operating on. What I don't understand, is why the conversion char -> MyChar isn't selected as the unambiguous conversion, since it is closer than char -> int -> MyInt. – bonio Oct 20 '21 at 11:38
  • describe the *actual* problem in the question itself. *None* of the conversions is closer than the other. All require implicit casts so none is closer than the other. For these examples there are multiple methods that could be called after implicit casts. – Panagiotis Kanavos Oct 20 '21 at 11:46
  • `as in the user's mind the operation is the same thing, irrespective of the type its operating on.` why not use a generic method then? Provide an example of the *actual* problem. You could use a common interface for all types, use generic methods or use some other trick – Panagiotis Kanavos Oct 20 '21 at 11:48
  • @PanagiotisKanavos - I'm not sure where you're going with your talk of the possible implicit casts, since the compiler should only ever be selecting *one* cast, not following a (potentially infinite) chain of them. – Damien_The_Unbeliever Oct 20 '21 at 12:07
  • @Damien_The_Unbeliever I'm not going anywhere, I tried this in DotNetFiddle. The compiler can use either method, so it complains. It doesn't try to see which is "closer". That may not be how things work internally, but the end result is that the compiler considers both options viable and refuses to pick one – Panagiotis Kanavos Oct 20 '21 at 13:45
  • @PanagiotisKanavos I was indeed asking for an explanation of how it works internally, or at least a model of how it works that would explain the behaviour I observe. I would find this useful when I come to imagine solutions to other similar problems I might come across. – bonio Oct 20 '21 at 14:18

1 Answers1

0

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
Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236