2016-03-03 97 views
4

为什么字符串插值宁愿使用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)); 

现在,我想有IsDebugEnabled逻辑里面Debug(IFormattable),并且在消息对象调用toString()只在必要时。

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

但是,这称为Debug(string)过载。

+0

插值字符串解析字符串' '但隐式类型转换为'IFormattable'。所以,如果你'IFormattable msg = $“Message {expensiveObject}”; Log.Debug(msg);'你应该做生意。请参阅https://msdn.microsoft.com/en-gb/library/dn961160.aspx#Anchor_0 – spender

+0

请参阅[TryRoslyn](http://goo.gl/eiRtVr)上的此示例,IFormattable是烟雾和镜像,带格式()下面:) – PTwr

+0

你应该真的在这里使用'ConditionalAttribute'。 – leppie

回答

7

这是一个deliberate decision by the Roslyn team

我们普遍认为,图书馆将主要与它做不同的事情的方法不同的API名称来编写。因此,FormattableString和String之间的重载解析差异并不重要,所以字符串不妨取胜。因此,我们应该坚持一个简单的原则,即插入的字符串是一个字符串。故事结局。

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

某些库API真的希望消费者使用FormattableString,因为它更安全或更快。接受字符串的API和接受FormattableString的API实际上做了不同的事情,因此不应该在同一个名字上重载。

+1

哇,经常使用时这太令人讨厌了。 –

1

你需要将其转换为IFormattableFormattableString

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

你可以使用一个啃老把戏的简写强制转换为IFormattable

public static class FormattableExtensions 
{ 
    public static FormattableString FS(FormattableString formattableString) 
    { 
     return formattableString; 
    } 
} 

而且使用这种方式:

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

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

+0

尼斯破解。现在它调用正确的方法:[TryRoslyn](http://goo.gl/8ZhgxX)(检查IL以查看哪个超载被调用)。注意:string-> Formattable是通过[FormattableStringFactory.Create]完成的(https://msdn.microsoft.com/zh-CN/library/system.runtime.compilerservices.formattablestringfactory.create(v = vs.110).aspx) – PTwr

1

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

你只需要诱使编译器选择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"); 
    } 
}