2016-03-03 2 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 {дорогоеобъект} "; 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, который берет строку и API, который принимает FormattableString, фактически выполняет разные вещи и, следовательно, не должен перегружаться с тем же именем.

+1

Ничего себе это так раздражает при использовании его часто. –

1

Вы должны бросить его IFormattable или FormattableString:

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/en-US/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"); 
    } 
}