隐式转换的重载解决方案

Overload Resolution with implicit conversions

我基本上想要 string/FormattableString 两个单独的重载(背景是我想推动人们对日志消息使用字符串常量并通过结构化日志而不是日志消息传递参数以简化分析。所以FormattableString 日志记录方法将被废弃)。

现在由于编译器的工作方式,您不能直接重载这些方法,因为 FormattableString 在传递之前会转化为字符串。有效的是有一个定义隐式重载的包装器结构:

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 
    } 

}

到目前为止一切顺利。

我不明白的是:为什么删除

implicit operator StringIfNotFormattableStringAdapter(FormattableString fs)

导致调用Log($"Hello")变得不明确?

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

根据 C# 规范,Interpolated strings,存在从内插字符串到 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.

在提供的代码中,还有 stringStringIfNotFormattableStringAdapter 的转换。

方法调用

Log($"Hello");

可以解析为两种Log方法,因为内插字符串表达式$"Hello"可以是:

  • 隐式转换为 FormattableString 作为内插字符串;
  • 隐式转换为 StringIfNotFormattableStringAdapter 作为 string

此处编译器出现歧义,它需要额外的规则来解决这种歧义。为了解决歧义,编译器使用 C# 规范 Better Conversion Target (go to the bottom of the page 164) 中描述的规则。规则说:

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)

在提供的代码中,FormattableStringStringIfNotFormattableStringAdapter 转换更好,因为

  • 没有从 StringIfNotFormattableStringAdapterFormattableString
  • 的隐式转换

  • 存在从 FormattableStringStringIfNotFormattableStringAdapter 的隐式转换。

因此编译器更喜欢将内插字符串 $"Hello" 转换为 FormattableString 然后调用方法 Log(FormattableString).

Why does removing the

implicit operator StringIfNotFormattableStringAdapter(FormattableString fs)

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

因为当您删除此运算符时,第二条规则 ("an implicit conversion from FormattableString to StringIfNotFormattableStringAdapter exists") 会中断,现在编译器无法定义更好的转换目标。这会导致编译器出现歧义并发生编译错误。