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.