String Interpolation with Format Variable

String Interpolation with format variable

No, you can't use string interpolation with something other than a string literal as the compiler creates a "regular" format string even when you use string interpolation.

Because this:

string name = "bar";
string result = $"{name}";

is compiled into this:

string name = "bar";
string result = string.Format("{0}", name);

the string in runtime must be a "regular" format string and not the string interpolation equivalent.

You can use the plain old String.Format instead.

C# How to treat a string variable as interpolated string?

String interpolation is a compiler feature, so it cannot be used at runtime. This should be clear from the fact that the names of the variables in the scope will in general not be availabe at runtime.

So you will have to roll your own replacement mechanism. It depends on your exact requirements what is best here.

If you only have one (or very few replacements), just do

output = input.Replace("{date}", date);

If the possible replacements are a long list, it might be better to use

output = Regex.Replace(input, @"\{\w+?\}", 
match => GetValue(match.Value));

with

string GetValue(string variable)
{
switch (variable)
{
case "{date}":
return DateTime.Today.ToString("MMddyyyy");
default:
return "";
}
}

If you can get an IDictionary<string, string> mapping variable names to values you may simplify this to

output = Regex.Replace(input, @"\{\w+?\}", 
match => replacements[match.Value.Substring(1, match.Value.Length-2)]);

String interpolation with variable from resource

We have the FormattableString class and the FormattableStringFactory.

This is how to use them

string error = "Apple";

// This comes from your resourse.
string myErrorMessage = "Fruit with name '{0}' does not exist.";
FormattableString s = FormattableStringFactory.Create(myErrorMessage, error);
string message = s.ToString();

However you still need to change your resources files to be as expected by the FormattableStringFactory. You need also to add the System.Runtime.CompilerServices namespace to your project

How to use variable inside interpolated string?

You are missing a $

var name = "mike";
var desc = $"hello world {name}"; // this needs be interpolated as well
var t = $"{desc}";
Console.WriteLine(t); // PRINTS: hello world mike

Additional Resources

$ - string interpolation (C# Reference)

The $ special character identifies a string literal as an interpolated
string
. An interpolated string is a string literal that might contain
interpolated expressions. When an interpolated string is resolved to a
result string, items with interpolated expressions are replaced by the
string representations of the expression results. This feature is
available in C# 6 and later versions of the language.


Update

but suppose I want to have a variable storing the string with {name}
in it. Is there no way to achieve interpolation if its in a variable?

No you would have to use standard String.Format Tokens

var tokenString = "Something {0}";

String.Format(tokenString,someVariable);

String.Format Method

Converts the value of objects to strings based on the formats
specified and inserts them into another string.

Use String.Format if you need to insert the value of an object,
variable, or expression into another string. For example, you can
insert the value of a Decimal value into a string to display it to the
user as a single string:

Composite Formatting

The .NET composite formatting feature takes a list of objects and a
composite format string as input. A composite format string consists
of fixed text intermixed with indexed placeholders, called format
items, that correspond to the objects in the list. The formatting
operation yields a result string that consists of the original fixed text intermixed with the string representation of the objects in the list.

String interpolation with boolean formatting

Using C# 10.0? Just use a String Interpolation Handler

Custom String Interpolation Handlers are documented here and here

(I don't have any experience with any C# 10.0 features yet, but I'll expand this section in future - right now I'm still stuck in C# 7.3 land due to my day-job's projects' dependencies on .NET Framework 4.8)

Using C# 1.0 through C# 9.0?

Quick-fix: Boolean wrapper struct

If you control the string-formatting call-sites, then just change bool/Boolean-typed values to use an implicitly-convertible zero-overhead value-type instead, e.g.:

public readonly struct YesNoBoolean : IEquatable<YesNoBoolean>
{
// https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/user-defined-conversion-operators
public static implicit operator Boolean ( YesNoBoolean self ) => self.Value;
public static implicit operator YesNoBoolean( Boolean value ) => new MyBoolean( value );

// https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/true-false-operators
public static Boolean operator true( YesNoBoolean self ) => self.Value == true;
public static Boolean operator false( YesNoBoolean self ) => self.Value == false;

public YesNoBoolean( Boolean value )
{
this.Value = value;
}

public readonly Boolean Value;

public override String ToString()
{
return this.Value ? "Yes" : "No";
}

// TODO: Override Equals, GetHashCode, IEquatable<YesNoBoolean>.Equals, etc.
}

So your example call-site becomes:

double d = Math.PI;
DateTime now = DateTime.Now;
YesNoBoolean isPartyTime = true; // <-- Yay for implicit conversion.

string result = $"{d:0.0}, {now:HH:mm}, time to party? {isPartyTime}";

And result will be "3.1, 21:03, time to party? Yes"

Bubble-bursting: No, you can't overwrite Boolean.TrueString and FalseString

Because Boolean's static readonly String TrueString = "True"; is also marked with initonly you cannot overwrite it using reflection, so doing this:

typeof(Boolean).GetField( "TrueString" )!.SetValue( obj: null, value: "Yes" );

...will give you a runtime exception:

Cannot set initonly static field 'TrueString' after type 'System.Boolean' is initialized.

It is still possible by manipulating raw memory, but that's out-of-scope for this question.

Using IFormatProvider and ICustomFormatter:

It's always been possible to override how both String.Format and interpolated strings (e.g. $"Hello, {world}") are formatted by providing a custom IFormatProvider; though while String.Format makes it easy by exposing a Format overload parameter, interpolated strings do not, instead it forces you to uglify your code somewhat.

  • Implementing IFormatProvider is (still) surprisingly underdocumented in .NET.
    • The main thing to remember is that IFormatProvider.GetFormat(Type) is only ever invoked with one of these 3 formatType arguments:
      • typeof(DateTimeFormatInfo)
      • typeof(NumberFormatInfo)
      • typeof(ICustomFormatter)
    • Throughout the entire .NET BCL, no other typeof() types are passed into GetFormat (at least as far as ILSpy and RedGate Reflector tell me).

The magic happens inside ICustomFormatter.Format and implementing it is straightforward:

public class MyCustomFormatProvider : IFormatProvider
{
public static readonly MyCustomFormatProvider Instance = new MyCustomFormatProvider();

public Object? GetFormat( Type? formatType )
{
if( formatType == typeof(ICustomFormatter) )
{
return MyCustomFormatter.Instance;
}

return null;
}
}

public class MyCustomFormatter : ICustomFormatter
{
public static readonly MyCustomFormatter Instance = new MyCustomFormatter();

public String? Format( String? format, Object? arg, IFormatProvider? formatProvider )
{
// * `format` is the "aaa" in "{0:aaa}"
// * `arg` is the single value
// * `formatProvider` will always be the parent instance of `MyCustomFormatProvider` and can be ignored.

if( arg is Boolean b )
{
return b ? "Yes" : "No";
}

return null; // Returning null will cause .NET's composite-string-formatting code to fall-back to test `(arg as IFormattable)?.ToString(format)` and if that fails, then just `arg.ToString()`.
}

public static MyFormat( this String format, params Object?[] args )
{
return String.Format( Instance, format: format, arg: args );
}
}

...so just pass MyCustomFormatProvider.Instance into String.Format somehow, like below.

double d = Math.PI;
DateTime now = DateTime.Now;
bool isPartyTime = true;

string result1 = String.Format( MyCustomFormatProvider.Instance, "{0:0.0}, {1:HH:mm}, time to party? {2}", d, now, isPartyTime );

// or add `using static MyCustomFormatProvider` and use `MyFormat` directly:
string result2 = MyFormat( "{0:0.0}, {1:HH:mm}, time to party? {2}", d, now, isPartyTime );

// or as an extension method:
string result3 = "{0:0.0} {1:HH:mm}, time to party? {2}".MyFormat( d, now, isPartyTime );

// Assert( result1 == result2 == result3 );

So that works for String.Format, but how can we use MyCustomFormatProvider with C# $"" interpolated strings...?

...with great difficulty, because the C# langauge team who designed the interpolated strings feature made it always pass provider: null so all values use their default (usually Culture-specific) formatting, and they didn't provide any way to easily specify a custom IFormatProvider, even though there's decades-old Static Code Analysis rule against relying on implicit use of CurrentCulture (though it's not uncommon for Microsoft to break their own rules...).

  • Unfortunately overwriting CultureInfo.CurrentCulture won't work because Boolean.ToString() doesn't use CultureInfo at all.

The difficulty stems from the fact that C# $"" interpolated strings are always implicitly converted to String (i.e. they're formatted immediately) unless the $"" string expression is directly assigned to a variable or parameter typed as FormattableString or IFormattable, but infuriatingly this does not extend to extension methods (so public static String MyFormat( this FormattableString fs, ... ) won't work.

The the only thing that can be done here is to invoke that String MyFormat( this FormattableString fs, ... ) method as a (syntactically "normal") static method call, though using using static MyFormattableStringExtensions somewhat reduces the ergonomics problems - even more-so if you use global-usings (which requires C# 10.0, which already supports custom interpolated-string handlers, so that's kinda moot).

But like this:

public static class MyFormattableStringExtensions
{
// The `this` modifier is unnecessary, but I'm retaining it just-in-case it's eventually supported.
public static String MyFmt( this FormattableString fs )
{
return fs.ToString( MyCustomFormatProvider.Instance );
}
}

And used like this:

using static MyFormattableStringExtensions;

// ...

double d = Math.PI;
DateTime now = DateTime.Now;
bool isPartyTime = true;

string result = MyFmt( $"{d:0.0}, {now:HH:mm}, time to party? {isPartyTime}" );
Assert.AreEqual( result, "3.1, 23:05, time to party? Yes" );

Or just mutate FormattableString's arguments array

  • Seeming as there's no alternative to wrapping an interpolated string in a function call (like MyFmt( $"" ) above), there's a simpler alternative approach to having to implement IFormatProvider and ICustomFormatter: just edit the FormattableString's value arguments array directly.
  • Because this approach is significantly simpler it is preferable if you don't also need to format Boolean values in String.Format(IFormatProvider, String format, ...).
  • Like so:
public static class MyFormattableStringExtensions
{
public static String MyFmt( this FormattableString fs )
{
if( fs.ArgumentCount == 0 ) return fs.Format;
Object?[] args = fs.GetArguments();
for( Int32 i = 0; i < args.Length; i++ )
{
if( args[i] is Boolean b )
{
args[i] = b ? "Yes" : "No";
}
}
return String.Format( CultureInfo.CurrentCulture, fs.Format, arg: args );
}
}

And used just like before to get the same results:

using static MyFormattableStringExtensions;

// ...

double d = Math.PI;
DateTime now = DateTime.Now;
bool isPartyTime = true;

string result = MyFmt( $"{d:0.0}, {now:HH:mm}, time to party? {isPartyTime}" );
Assert.AreEqual( result, "3.1, 23:05, time to party? Yes" );

A constant value is expected in string interpolation

In this case you might be better off to use string.Format than interpolation.

var str = "test";
var width = 10;
var fmt = $"{{0,{width}}}";
var result = string.Format(fmt, str);

But if you're simply padding with spaces PadLeft or PadRight would be cleaner...

return str.PadLeft(width, ' ');


Related Topics



Leave a reply



Submit