使用隐式转换重载解析

Voo*_*Voo 5 c# language-lawyer overload-resolution

我基本上想要 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 
    } 

}
Run Code Online (Sandbox Code Playgroud)

到现在为止还挺好。

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

implicit operator StringIfNotFormattableStringAdapter(FormattableString fs)
Run Code Online (Sandbox Code Playgroud)

导致调用Log($"Hello")变得模棱两可?

CS0121 以下方法或属性之间的调用不明确:Test.Log(StringIfNotFormattableStringAdapter)' 和 'Test.Log(FormattableString)'`

Ili*_*hev 5

根据 C# 规范Interpolated strings,存在从内插字符串到 的隐式转换FormattableString

一个interpolated_string_expression被归类为一个值。如果它立即转换为System.IFormattableSystem.FormattableString使用隐式内插字符串转换(隐式内插字符串转换),则内插字符串表达式具有该类型。否则,它的类型为string

在提供的代码中也有stringto 的转换StringIfNotFormattableStringAdapter

方法调用

Log($"Hello");
Run Code Online (Sandbox Code Playgroud)

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

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

这里编译器出现歧义,它需要额外的规则来解决这种歧义。为了解决歧义,编译器使用 C# 规范中描述的规则,更好的转换目标(转到第 164 页的底部)。规则是这样说的:

给定两种不同的类型T1and T2T1T2不存在从T2to 的隐式转换相比,是更好的转换目标T1,并且至少满足以下条件之一:

  • T1T2存在的隐式转换

  • (其他规则对我们的案例并不重要)

在提供的代码FormattableString中转换比StringIfNotFormattableStringAdapter因为更好

  • 没有从StringIfNotFormattableStringAdapter到的隐式转换FormattableString

  • 存在从FormattableString到的隐式转换StringIfNotFormattableStringAdapter

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

为什么删除

implicit operator StringIfNotFormattableStringAdapter(FormattableString fs)
Run Code Online (Sandbox Code Playgroud)

导致调用Log($"Hello")变得模棱两可?

因为当您删除此运算符时,第二条规则(“从FormattableStringStringIfNotFormattableStringAdapter存在的隐式转换”)会中断,现在编译器无法定义更好的转换目标。这会导致编译器产生歧义并发生编译错误。