C# 6.0 String Interpolation, FormattableString, and Code Analysis CA1305: Specify IFormatProvider

C# 6.0 introduces a syntactic sugar string interpolation, it is safer and more readable than composite formatting. Here is a small example:

using System;
using System.Diagnostics;

internal static class Program
{
    private static void Main() => Trace.WriteLine($"Machine name: {Environment.MachineName}.");
}

However, string interpolation does not get along with code analysis. By default, the $ syntax will be compiled to composite formatting, by calling the string.Format overload without IFormatProvider parameter:

using System;
using System.Diagnostics;

internal static class Program
{
    private static void Main() => Trace.WriteLine(string.Format("Machine name: {0}.", Environment.MachineName));
}

As a result, Code Analysis/FxCop issues a CA1305 warning for every interpolated string: Specify IFormatProvider. This is very annoying.

Interpolated string has a infamous feature, it can be also compiled to System.FormattableString:

namespace System
{
    using System.Globalization;

    public abstract class FormattableString : IFormattable
    {
        protected FormattableString() { }

        public abstract string Format { get; }

        public abstract int ArgumentCount { get; }

        public abstract object[] GetArguments();

        public abstract object GetArgument(int index);

        public abstract string ToString(IFormatProvider formatProvider);

        string IFormattable.ToString(string ignored, IFormatProvider formatProvider) => this.ToString(formatProvider);

        public static string Invariant(FormattableString formattable)
        {
            if (formattable == null)
            {
                throw new ArgumentNullException(nameof(formattable));
            }

            return formattable.ToString(CultureInfo.InvariantCulture);
        }

        public override string ToString() => this.ToString(CultureInfo.CurrentCulture);
    }
}

Here FormattableString.Invariant seems to be a solution. Notice FormattableString is an abstract class. It is inherited by System.Runtime.CompilerServices.FormattableStringFactory.ConcreteFormattableString:

namespace System.Runtime.CompilerServices
{
    public static class FormattableStringFactory
    {
        private sealed class ConcreteFormattableString : FormattableString
        {
            private readonly string _format;

            private readonly object[] _arguments;

            public override string Format => this._format;

            public override int ArgumentCount => this._arguments.Length;

            internal ConcreteFormattableString(string format, object[] arguments)
            {
                this._format = format;
                this._arguments = arguments;
            }

            public override object[] GetArguments() => this._arguments;

            public override object GetArgument(int index) => this._arguments[index];

            public override string ToString
                (IFormatProvider formatProvider) => string.Format(formatProvider, this._format, this._arguments);
        }

        public static FormattableString Create(string format, params object[] arguments)
        {
            if (format == null)
            {
                throw new ArgumentNullException(nameof(format));
            }

            if (arguments == null)
            {
                throw new ArgumentNullException(nameof(arguments));
            }

            return new ConcreteFormattableString(format, arguments);
        }
    }
}

So that, FormattableString.Invariant calls ConcreteFormattableString.ToString, which then calls string.Format, the overload with IFormatProvider. Code Analysis warning CA1305: Specify IFormatProvider can be fixed as:

using System;
using System.Diagnostics;

using static System.FormattableString;

internal static class Program
{
    private static void Main() => Trace.WriteLine(Invariant($"Machine name: {Environment.MachineName}."));
}

Above interpolated string is compiled to composite formatting call to FormattableStringFactory.Create:

using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;

using static System.FormattableString;
internal static class Program
{
    private static void Main() => Trace.WriteLine(Invariant(
        // $"Machine name: {Environment.MachineName}." is compiled to:
        FormattableStringFactory.Create("Machine name: {0}.", Environment.MachineName)));
}

So the conclusion is, to fix Code Analysis CA1305 for C# 6.0 string interpolation, FormattableString.Invariant has to be called for every occurrence of $ syntax. This is still very annoying. Hope there can be another syntactic sugar for this, for example, a $$ prefix to call FormattableString.Invariant.

Also, MSDN and many other articles are inaccurate about interpolated string and FormattableString. MSDN says:

There are implicit type conversions from an interpolated string

In .NET, the term “implicit type conversion” is usually about runtime behavior, implemented by calling a type conversion operator defined with the implicit keyword. However, as demonstrated above, interpolated string becomes FormattableString/IFormattable at compile time.

5 Comments

  • Never thought about it.

  • Now I start to understand something more clearly! it's cool!

  • Thank you for this explanation! Even though it is still annoying, at least it solves my problem with the Code Analysis warnings!

  • Using Invariant everywhere defeats any culture-sensitive string formatting. That can be good if a machine will parse your string later. But if it's for human presentation, you should keep it culture sensitive. You can do this with string interpolation, although it's less convenient:

    static string StringInterpolation_CultureSensitive()
    {
    return ((FormattableString)$"hi {3 + 5}").ToString(CultureInfo.CurrentCulture);
    }

    static string StringInterpolation_WithInvariant()
    {
    return Invariant($"hi {3 + 5}");
    }

  • @Andrew Arnott, Calling .ToString() is already culture-sensitive. See https://gist.github.com/binki/d2457e592c51b507cb77bb1c877e3175 and the documentation for FormattableString.ToString() (the overload taking no arguments talks about how it works “by using the formatting conventions of the current culture”). So, with string interpolation, supporting the current culture is actually quite easy. Just use let it compile as something that evaluates to string rather than something else and you’re done!

Add a Comment

As it will appear on the website

Not displayed

Your website