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 3formatType
arguments:typeof(DateTimeFormatInfo)
typeof(NumberFormatInfo)
typeof(ICustomFormatter)
- Throughout the entire .NET BCL, no other
typeof()
types are passed intoGetFormat
(at least as far as ILSpy and RedGate Reflector tell me).
- The main thing to remember is that
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 becauseBoolean.ToString()
doesn't useCultureInfo
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 implementIFormatProvider
andICustomFormatter
: just edit theFormattableString
's value arguments array directly. - Because this approach is significantly simpler it is preferable if you don't also need to format
Boolean
values inString.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
Is This Thread.Abort() Normal and Safe
Easier Way of Writing Null or Empty
Crud Operations Using Datagridview, Datatable and Dataadapter - Cannot Add New Row to Datagridview
Auto Versioning in Visual Studio 2017 (.Net Core)
Automatically Update Version Number
Put Content in Httpresponsemessage Object
Bind Datagrid Column Visibility Mvvm
Should We Always Include a Default Constructor in the Class
Winforms Issue - Error Creating Window Handle
How to Optionally Turn Off the JSONignore Attribute at Runtime
How to Get Property Change Notifications with Ef 4.X Dbcontext Generator
A Dictionary Object That Uses Ranges of Values for Keys
Ef4 Code First: How to Add a Relationship Without Adding a Navigation Property
Restsharp Simple Complete Example
In C# Differencebetween a Destructor and a Finalize Method in a Class