问题
Why does string interpolation prefer overload of method with string
instead of IFormattable
?
Imagine following:
static class Log {
static void Debug(string message);
static void Debug(IFormattable message);
static bool IsDebugEnabled { get; }
}
I have objects with very expensive ToString()
. Previously, I did following:
if (Log.IsDebugEnabled) Log.Debug(string.Format("Message {0}", expensiveObject));
Now, I wanted to have the IsDebugEnabled logic inside Debug(IFormattable)
, and call ToString() on objects in message only when necessary.
Log.Debug($"Message {expensiveObject}");
This, however, calls the Debug(string)
overload.
回答1:
This is a deliberate decision by the Roslyn team:
We generally believe that libraries will mostly be written with different API names for methods which do different things. Therefore overload resolution differences between FormattableString and String don't matter, so string might as well win. Therefore we should stick with the simple principle that an interpolated string is a string. End of story.
There's more discussion about this in the link, but the upshot is they expect you to use different method names.
Some library APIs really want consumers to use FormattableString because it is safer or faster. The API that takes string and the API that takes FormattableString actually do different things and hence shouldn't be overloaded on the same name.
回答2:
Realizing you ask why you can't do this, I'd just like to point out that you can in-fact do this.
You just need to trick the compiler into preferring the FormattableString overload. I've explained it in details here: https://robertengdahl.blogspot.com/2016/08/how-to-overload-string-and.html
And here is the test code:
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"));
}
}
And here is the code that makes this work:
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");
}
}
回答3:
You need to cast it to IFormattable
or FormattableString
:
Log.Debug((IFormattable)$"Message {expensiveObject}");
You could use a neet trick as a shorthand for a cast to IFormattable
:
public static class FormattableExtensions
{
public static FormattableString FS(FormattableString formattableString)
{
return formattableString;
}
}
And use it this way:
Log.Debug(FS($"Message {expensiveObject}"));
I expect the JIT compiler to inline FS
in production.
来源:https://stackoverflow.com/questions/35770713/overloaded-string-methods-with-string-interpolation