57

FormattableString has been Introduced in C# 6.0. As we can use same string formatting using string object why is there need of using FormattableString or IFormattable. Whats difference between three?

My Code

        var name = "Pravin";
        var s = $"Hello, {name}";
        System.IFormattable s1 = $"Hello, {name}";
        System.FormattableString s2 = $"Hello, {name}";

Above all there produces same result. i.e 'Hello Pravin'.

Can I get more elaborated answer on this if anyone has deep knowledge on same.

Eugene
  • 10,957
  • 20
  • 69
  • 97
Nitin Varpe
  • 10,450
  • 6
  • 36
  • 60

2 Answers2

78

FormattableString is a new type in .NET 4.6, and the compiler will only use it if you try to use it. In other words, the type of an interpolated string literal is normally string - built with string.Format - but can be FormattableString (via FormattableStringFactory) if you ask for it.

A FormattableString consists of the format string which would be passed to string.Format (e.g. "Hello, {0}") and the arguments that would be passed in order to format it. Crucially, this information is captured before formatting.

This allows you to adjust the formatting appropriately - most commonly to perform it in the invariant culture, often with the Invariant static method.

When you assign an interpolated string literal to an IFormattable variable, that will use FormattableString too. The IFormattable.ToString(string, CultureInfo) implementation ignores the first argument in this case, which is presumably why it uses explicit interface implementation.

Sample code:

using System;
using System.Globalization;
using System.Threading;
using static System.FormattableString;

class Test
{
    static void Main()
    {
        var uk = CultureInfo.CreateSpecificCulture("en-GB");
        Thread.CurrentThread.CurrentCulture = uk;
        var germany = CultureInfo.CreateSpecificCulture("de-DE");
        string now = $"Default: it is now {DateTime.UtcNow}";
        Console.WriteLine(now); // UK format
        IFormattable x = $"Specific: It is now {DateTime.UtcNow}";
        Console.WriteLine(x.ToString("ignored", germany));
        FormattableString y = $"FormattableString: It is now {DateTime.UtcNow}";
        Console.WriteLine(FormattableString.Invariant(y));
        // Via using static
        Console.WriteLine(Invariant($"It is now {DateTime.UtcNow}")); 
    }
}

Sample results:

Default: it is now 16/02/2016 07:16:21
Specific: It is now 16.02.2016 07:16:21
FormattableString: It is now 02/16/2016 07:16:21
It is now 02/16/2016 07:16:21
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 5
    Since `string` does not implement `IFormattable`, using `FormattableString` is only choice, when you cast interpolated string to `IFormattable`. – user4003407 Feb 16 '16 at 07:31
  • @PetSerAI: You're absolutely right. Not sure what I was thinking. Will edit. When I get to the office... – Jon Skeet Feb 16 '16 at 07:33
  • @JonSkeet Just looking at a line of code from the Microsoft open XML SDK: `RawOuterXml = Invariant($"");` Where RawOuterXml and xmlReader.Value are both strings. Will the Invariant call do anything to a variable that is already a string? – axeman Apr 07 '20 at 20:25
  • @axeman: I suspect not, no - but it may be one of those situations where it's easier to make multiple calls use `Invariant` than to reason about each one individually. – Jon Skeet Apr 08 '20 at 06:11
7

As an aside, https://www.meziantou.net/interpolated-strings-advanced-usages.htm covers some examples of what FormattableString allows you to do (e.g. auto-parameterising SQL statements)

e.g.

void ExecuteNonQuery(DbConnection connection, FormattableString formattableString)
{
    using (var command = connection.CreateCommand())
    {
        // Replace values by @p0, @p1, @p2, ....
        var args = Enumerable.Range(0, formattableString.ArgumentCount).Select(i => (object)("@p" + i)).ToArray();

        command.CommandType = System.Data.CommandType.Text;
        command.CommandText = string.Format(formattableString.Format, args);

        // Create parameters
        for (var i = 0; i < formattableString.ArgumentCount; i++)
        {
            var arg = formattableString.GetArgument(i);
            var p = command.CreateParameter();
            p.ParameterName = "@p" + i;
            p.Value = arg;
            command.Parameters.Add(p);
        }

        // Execute the command
        command.ExecuteNonQuery();
    }
}

using (var sqlConnection = new SqlConnection())
{
    sqlConnection.Open();
    ExecuteNonQuery(sqlConnection, $@"UPDATE Customers SET Name = {"Meziantou"} WHERE Id = {1}");
}

Dave Glassborow
  • 3,253
  • 1
  • 30
  • 25