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.
As the colon (":") has special meaning in an interpolation expression item, to use a conditional operator in an interpolation expression, enclose that expression in parentheses. string name = "Horace"; int age = 34; Console. WriteLine($"He asked, \"Is your name {name}?\
The string interpolation feature is built on top of the composite formatting feature and provides a more readable and convenient syntax to include formatted expression results in a result string.
String Interpolation is a process in which the placeholder characters are replaced with the variables (or strings in this case) which allow to dynamically or efficiently print out text output. String Interpolation makes the code more compact and avoids repetition of using variables to print the output.
Concatenation allows you to combine to strings together and it only works on two strings. Swift uses string interpolation to include the name of a constant or variable as a placeholder in a longer string, and to prompt Swift to replace it with the current value of that constant or variable.
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.
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");
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With