【问题标题】:Overload Resolution with implicit conversions具有隐式转换的重载解决方案
【发布时间】:2020-03-20 16:41:17
【问题描述】:

我基本上想要两个单独的 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 以下方法或属性之间的调用不明确:Test.Log(StringIfNotFormattableStringAdapter)' 和 'Test.Log(FormattableString)'`

【问题讨论】:

  • 仅仅因为您提到了结构化日志记录:另一种方法是使用/编写检测功能滥用的 Roslyn-Analyzer。 Serilog 存在one。不幸的是,Microsoft.Extensions.Logging 的一个仍在progress 中。还有一个 Resharper 扩展做同样的事情和更多(支持更多的日志框架、标记属性、模板突出显示)。
  • @PEterE 是的,我过去曾为某些事情编写过自定义分析器。我们有自己的 nlog 包装器,因此现有的分析器无法工作。与简单的带注释的重载相比,编写分析器需要更多的工作和更难维护。但是是的,这也可以,并且可以捕获更多的东西。

标签: c# language-lawyer overload-resolution


【解决方案1】:

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

interpolated_string_expression 被归类为一个值。如果是 立即转换为System.IFormattableSystem.FormattableString 带有隐式插值字符串 转换(Implicit interpolated string conversions), 插值字符串表达式具有该类型。否则,它具有 输入string

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

方法调用

Log($"Hello");

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

  • 隐式转换为FormattableString作为插值字符串;
  • 隐式转换为StringIfNotFormattableStringAdapterstring

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

给定两种不同的类型 T1T2T1 是更好的转换 目标比T2 不存在从T2T1 的隐式转换, 并且至少满足以下一项:

  • 存在从T1T2 的隐式转换

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

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

  • 没有从StringIfNotFormattableStringAdapterFormattableString 的隐式转换

  • 存在从FormattableStringStringIfNotFormattableStringAdapter 的隐式转换。

因此编译器倾向于将插值字符串$"Hello" 转换为FormattableString,然后调用方法Log(FormattableString)

为什么要删除

implicit operator StringIfNotFormattableStringAdapter(FormattableString fs)

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

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

【讨论】:

    猜你喜欢
    • 2021-12-10
    • 1970-01-01
    • 1970-01-01
    • 2017-03-05
    • 2016-07-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多