使用字符串插值重载的字符串方法

时间:2016-03-03 11:17:28

标签: c# c#-6.0 string-interpolation

为什么字符串插值更喜欢使用string代替IFormattable的方法重载?

想象一下:

static class Log {
    static void Debug(string message);
    static void Debug(IFormattable message);
    static bool IsDebugEnabled { get; }
}

我的对象非常昂贵ToString()。以前,我做过以下事情:

if (Log.IsDebugEnabled) Log.Debug(string.Format("Message {0}", expensiveObject));

现在,我想在Debug(IFormattable)内部使用IsDebugEnabled逻辑,并且只在必要时才在消息中的对象上调用ToString()。

Log.Debug($"Message {expensiveObject}");

然而,这会调用Debug(string)重载。

4 个答案:

答案 0 :(得分:10)

这是deliberate decision by the Roslyn team

  

我们通常认为,对于执行不同操作的方法,库主要使用不同的API名称编写。因此,FormattableString和String之间的重载分辨率差异无关紧要,因此字符串也可能获胜。因此,我们应该坚持插值字符串是一个字符串的简单原则。故事结束。

链接中有关于此的更多讨论,但结果是他们希望您使用不同的方法名称。

  

某些库API确实希望使用者使用FormattableString,因为它更安全或更快。采用字符串的API和采用FormattableString的API实际上做了不同的事情,因此不应该重载相同的名称。

答案 1 :(得分:6)

意识到你问为什么你不能这样做,我只想指出你事实上可以做到这一点。

您只需要欺骗编译器优先选择FormattableString重载。我在这里详细解释了它:https://robertengdahl.blogspot.com/2016/08/how-to-overload-string-and.html

这是测试代码:

public class StringIfNotFormattableStringAdapterTest
{
    public interface IStringOrFormattableStringOverload
    {
        void Overload(StringIfNotFormattableStringAdapter s);
        void Overload(FormattableString s);
    }

    private readonly IStringOrFormattableStringOverload _stringOrFormattableStringOverload =
        Substitute.For<IStringOrFormattableStringOverload>();

    public interface IStringOrFormattableStringNoOverload
    {
        void NoOverload(StringIfNotFormattableStringAdapter s);
    }

    private readonly IStringOrFormattableStringNoOverload _noOverload =
        Substitute.For<IStringOrFormattableStringNoOverload>();

    [Fact]
    public void A_Literal_String_Interpolation_Hits_FormattableString_Overload()
    {
        _stringOrFormattableStringOverload.Overload($"formattable string");
        _stringOrFormattableStringOverload.Received().Overload(Arg.Any<FormattableString>());
    }

    [Fact]
    public void A_String_Hits_StringIfNotFormattableStringAdapter_Overload()
    {
        _stringOrFormattableStringOverload.Overload("plain string");
        _stringOrFormattableStringOverload.Received().Overload(Arg.Any<StringIfNotFormattableStringAdapter>());
    }

    [Fact]
    public void An_Explicit_FormattableString_Detects_Missing_FormattableString_Overload()
    {
        Assert.Throws<InvalidOperationException>(
            () => _noOverload.NoOverload((FormattableString) $"this is not allowed"));
    }
}

以下是使这项工作的代码:

public class StringIfNotFormattableStringAdapter
{
    public string String { get; }

    public StringIfNotFormattableStringAdapter(string s)
    {
        String = s;
    }

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

    public static implicit operator StringIfNotFormattableStringAdapter(FormattableString fs)
    {
        throw new InvalidOperationException(
            "Missing FormattableString overload of method taking this type as argument");
    }
}

答案 2 :(得分:2)

您不能强迫编译器选择IFormattable / FormattableString而不是String,但是可以使编译器选择IFormattable / FormattableString而不是Object:

static class Log
{
    static void Debug(object message);
    static void Debug(IFormattable message);
    static void Debug(FormattableString message);
    static bool IsDebugEnabled { get; }
}

此解决方案的成本是采用Object的方法中的额外ToString()调用。 (FormattableString的额外重载实际上不是必需的,但是它将简化查找在何处使用插值strign的功能)

答案 3 :(得分:1)

您需要将其投放到IFormattableFormattableString

Log.Debug((IFormattable)$"Message {expensiveObject}");

您可以使用neet技巧作为IFormattable

的演员的简写
public static class FormattableExtensions
{
    public static FormattableString FS(FormattableString formattableString)
    {
        return formattableString;
    }
}

并以这种方式使用它:

Log.Debug(FS($"Message {expensiveObject}"));

我希望JIT编译器在生产中内联FS