Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overload Resolution with implicit conversions

I basically want two separate overloads for string/FormattableString (the background is that I want to nudge people towards using string constants for log messages and pass parameters via structured logging instead of the log message to simplify analysis. So the FormattableString logging method would be obsoleted).

Now due to the way the compiler works, you cannot directly overload the methods, because a FormattableString devolves to a string before it's being passed. What does work though is to have a wrapper struct that defines implicit overloads:

public struct StringIfNotFormattableStringAdapter
{
    public string StringValue { get; }

    private StringIfNotFormattableStringAdapter(string s)
    {
        StringValue = s;
    }

    public static implicit operator StringIfNotFormattableStringAdapter(string s)
    {
        return new StringIfNotFormattableStringAdapter(s);
    }

    public static implicit operator StringIfNotFormattableStringAdapter(FormattableString fs)
    {
        throw new InvalidOperationException("This only exists to allow correct overload resolution. " +
                                            "This should never be called since the FormattableString overload should be preferred to this.");
    }
}

public static class Test
{
    public static void Log(StringIfNotFormattableStringAdapter msg)
    {
    }

    public static void Log(FormattableString msg)
    {
    }

    public static void Foo() 
    {
         Log("Hello"); // resolves to StringIfNotFormattableStringAdapter overload
         Log($"Hello"); // resolves to FormattableString overload 
    } 

}

So far so good.

What I don't understand: Why does removing the

implicit operator StringIfNotFormattableStringAdapter(FormattableString fs)

cause the call Log($"Hello") to become ambiguous?

CS0121 The call is ambiguous between the following methods or properties: Test.Log(StringIfNotFormattableStringAdapter)' and 'Test.Log(FormattableString)'`

like image 491
Voo Avatar asked Mar 20 '20 16:03

Voo


1 Answers

According to C# Specification, Interpolated strings, there is an implicit conversion from interpolated string to FormattableString:

An interpolated_string_expression is classified as a value. If it is immediately converted to System.IFormattable or System.FormattableString with an implicit interpolated string conversion (Implicit interpolated string conversions), the interpolated string expression has that type. Otherwise, it has the type string.

In the provided code there is also conversion of string to StringIfNotFormattableStringAdapter.

Method invocation

Log($"Hello");

can be resolved to both Log methods, because interpolated string expression $"Hello" can be:

  • implicitly converted to FormattableString as interpolated string;
  • implicitly converted to StringIfNotFormattableStringAdapter as string.

Here an ambiquity occurs to compiler and it needs additional rules to resolve this ambiguity. To resolve the ambiguity compiler uses rules that are described in C# Specification, Better Conversion Target (go to the bottom of the page 164). The rules say that:

Given two different types T1 and T2 , T1 is a better conversion target than T2 if no implicit conversion from T2 to T1 exists, and at least one of the following holds:

  • An implicit conversion from T1 to T2 exists

  • (other rules are not important for our case)

In the provided code FormattableString is better conversion than StringIfNotFormattableStringAdapter because

  • there is no implicit conversion from StringIfNotFormattableStringAdapter to FormattableString

and

  • an implicit conversion from FormattableString to StringIfNotFormattableStringAdapter exists.

Therefore compiler prefers to convert interpolated string $"Hello" to FormattableString and then invokes method Log(FormattableString).

Why does removing the

implicit operator StringIfNotFormattableStringAdapter(FormattableString fs)

cause the call Log($"Hello") to become ambiguous?

Because when you remove this operator the second rule ("an implicit conversion from FormattableString to StringIfNotFormattableStringAdapter exists") breaks and now compiler cannot define better conversion target. This causes an ambiguity to compiler and compilation error occurs.

like image 68
Iliar Turdushev Avatar answered Sep 18 '22 23:09

Iliar Turdushev