Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overloaded string methods with string interpolation

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.

like image 766
nothrow Avatar asked Mar 03 '16 11:03

nothrow


People also ask

How do you use string interpolation?

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}?\

Why do we use string interpolation?

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.

What is string interpolation in Java?

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.

What's the difference between concatenation and interpolation?

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.


2 Answers

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.

like image 52
Rawling Avatar answered Nov 07 '22 10:11

Rawling


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");
    }
}
like image 43
Robert Jørgensgaard Engdahl Avatar answered Nov 07 '22 10:11

Robert Jørgensgaard Engdahl