为什么字符串插值更喜欢使用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)
重载。
答案 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)
您需要将其投放到IFormattable
或FormattableString
:
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
。