2016-03-03 85 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"); 
    } 
}