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)'`