Search code examples
c#.netstringformattablestringiformattable

C# question: Why cannot I assign a string directly to a class inheriting System.IFormattable or System.FormattableString while they themselves can?


public class Program
{
    public void Main()
    {
        IFormattable string1 = $"{1},{"test"}";      //Works
        FormattableString string2 = $"{1},{"test"}"; //Works
        LocStr string3 = $"{1},{"test"}"; ///Error: Cannot implicitly convert type 'string' to 'LocStr'

    }
    public class LocStr : IFormattable
    {
        public string ToString(string format, IFormatProvider formatProvider)
        {
            throw new NotImplementedException();
        }
    }
}

I am trying to create my own custon FormattableString class, however whether my class inherits from IFormattable or FormattableString, it cannot be directly be assigned from a formated string, as in LocStr testStr= $"Hello {User.Name}";

I tried inheriting from IFormattable, FormattableString, neither worked.

I checked if I can override assignment operator = but it is apparently not allowed in C# as in public static LocStr operator =(string a) but it is not allowed.


Solution

  • There is some compiler magic going behind the scenes. If you define variable type as explicitly string or allow compiler to determine it with var then it will result in type string:

    var string0 = $"{1},{"test"}";
    Console.WriteLine(string0.GetType()); // System.String
    

    But for formattable strings there is also a special type FormattableString:

    A FormattableString instance may result from an interpolated string in C# or Visual Basic.

    which you can actually use in your code which results in the following behavior:

    IFormattable string1 = $"{1},{"test"}";      //Works
    FormattableString string2 = $"{1},{"test"}"; //Works
    
    Console.WriteLine(string1.GetType()); // System.Runtime.CompilerServices.FormattableStringFactory+ConcreteFormattableString
    Console.WriteLine(string2.GetType()); // System.Runtime.CompilerServices.FormattableStringFactory+ConcreteFormattableString
    

    Demo @sharplab.io (also check out the decompilation to C#).

    This is actually described in Implicit conversions and how to specify IFormatProvider implementation section of the docs:

    There are three implicit conversions from an interpolated string:

    1. Conversion of an interpolated string to a String instance. ...
    2. Conversion of an interpolated string to a FormattableString instance that represents a composite format string along with the expression results to be formatted. ...
    3. Conversion of an interpolated string to an IFormattable instance that also allows you to create multiple result strings with culture-specific content from a single IFormattable instance. ...

    Compiler out of the box does not know how to convert the interpolated string into your specific type but since C# 10 you can implement the interpolated string handler pattern (not that you should though in general case, usually it is needed for specific high performance scenarios):

    [InterpolatedStringHandler]
    public class LocStr : IFormattable
    {
        public LocStr(int literalLength, int formattedCount)
        {
            
        }
        public string ToString(string format, IFormatProvider formatProvider)
        {
            throw new NotImplementedException();
        }
        
        public void AppendLiteral(string s)
        {
        }
    
        public void AppendFormatted<T>(T t)
        {
        }
    }
    

    Updated demo @sharplab.io