The InterpolateExplicit() method is faster since we now explicitly tell the compiler to use a string . No need to box the object to be formatted.
In computer programming, string interpolation (or variable interpolation, variable substitution, or variable expansion) is the process of evaluating a string literal containing one or more placeholders, yielding a result in which the placeholders are replaced with their corresponding values.
Beginning with C# 10, you can use string interpolation to initialize a constant string. All expressions used for placeholders must be constant strings. In other words, every interpolation expression must be a string, and it must be a compile time constant.
String interpolation is a technique that enables you to insert expression values into literal strings. It is also known as variable substitution, variable interpolation, or variable expansion. It is a process of evaluating string literals containing one or more placeholders that get replaced by corresponding values.
The problem with this line
Assert.AreEqual(formatted, $"{{countdown|{date:o}}}");
is that you have 3 curly quotes after the format string
of the variable to be escaped and it starts escaping from left to right, therefore it treats the first 2 curly quotes as part of the format string and the third curly quote as the closing one.
So it transforms o
in o}
and the it's unable to interpolate it.
This should work
Assert.AreEqual(formatted, $"{{countdown|{date:o}"+"}");
Notice that the simpler $"{date}}}"
(i.e. 3 curls after the variable without a format string
) does work because it recognizes that the first curly quote is the closing one, while the interpretation of the format specifier after the :
breaks the correct closing parenthesis identification.
To prove that the format string is escaped like a string, consider that the following
$"{date:\x6f}"
is treated as
$"{date:o}"
Finally, it is perfectly possible that the double escaped curly quotes are part of a custom date format, so it is absolutely reasonable the behaviour of the compiler. Again, a concrete example
$"{date:MMM}}dd}}yyy}" // it's a valid feb}09}2017
Parsing is a formal process based on expression grammar rules, can't be done by just glancing at it.
This is a follow-up to my original answer in order
to make sure this is the intended behavior
As far as an official source is concerned, we should refer to the Interpolated Strings from msdn.
The structure of an interpolated string is
$ " <text> { <interpolation-expression> <optional-comma-field-width> <optional-colon-format> } <text> ... } "
and each single interpolation is formally defined with a syntax
single-interpolation:
interpolation-start
interpolation-start : regular-string-literal
interpolation-start:
expression
expression , expression
What counts here is that
optional-colon-format
is defined as a regular-string-literal
syntax => i.e. it can contains an escape-sequence
, according to the paragraph 2.4.4.5 String literals
of the C# Language Specification 5.0
string literal
{
or }
) in an interpolated string use two curly braces, {{
or }}
=> i.e. the compiler escapes two curly braces in the optional-colon-format
expressions
as balanced text until it finds a comma, colon, or close curly brace => i.e. a colon breaks the balanced text as well as a close curly braceJust to be clear, this explains the difference between $"{{{date}}}"
where date
is an expression
and so it is tokenized until the first curly brace versus $"{{{date:o}}}"
where date
is again an expression
and now it is tokenized until the first colon, after which a regular string literal begins and the compiler resumes escaping two curly braces, etc...
There is also the String Formatting FAQ from msdn, where this case was explicitly treated.
int i = 42;
string s = String.Format(“{{{0:N}}}”, i); //prints ‘{N}’
The question is, why did this last attempt fail? There’s two things you need to know in order to understand this result:
When providing a format specifier, string formatting takes these steps:
Determine if the specifier is longer than a single character: if so, then assume that the specifier is a custom format. A custom format will use suitable replacements for your format, but if it doesn’t know what to do with some character, it will simply write it out as a literal found in the format Determine if the single character specifier is a supported specifier (such as ‘N’ for number formatting). If it is, then format appropriately. If not, throw an
ArgumnetException
When attempting to determine whether a curly bracket should be escaped, the curly brackets are simply treated in the order they are received. Therefore,
{{{
will escape the first two characters and print the literal{
, and the the third curly bracket will begin the formatting section. On this basis, in}}}
the first two curly brackets will be escaped, therefore a literal}
will be written to the format string, and then the last curly bracket will be assumed to be ending a formatting section With this information, we now can figure out what’s occurring in our{{{0:N}}}
situation. The first two curly brackets are escaped, and then we have a formatting section. However, we then also escape the closing curly bracket, before closing the formatting section. Therefore, our formatting section is actually interpreted as containing0:N}
. Now, the formatter looks at the format specifier and it seesN}
for the specifier. It therefore interprets this as a custom format, and since neither N or } mean anything for a custom numeric format, these characters are simply written out, rather than the value of the variable referenced.
Problem seems to be that to insert a parenthesis while using string interpolation you you need to escape it by duplicating it. If you add the parenthesis used for the interpolation itself, we end up with a triple parenthesis such as the one you have in the line that gives you the exception:
Assert.AreEqual(formatted, $"{{countdown|{date:o}}}");
Now, if we observe the "}}}", we can notice that the first parenthesis encloses the string interpolation, while the final two are meant to be treated as a string-escaped parenthesis character.
The compiler however, is treating the first two as the scaped string character, thus it's inserting a string between the interpolation delimiters. Basically the compiler is doing something like this:
string str = "a string";
$"{str'}'}"; //this would obviously generate a compile error which is bypassed by this bug
You can resolve this by reformatting the line as such:
Assert.AreEqual(formatted, $"{{countdown|{$"{date:o}"}}}");
This is the easiest way to get the assert to work...
Assert.AreEqual(formatted, "{" + $"countdown|{date:o}" + "}");
In this form...
Assert.AreEqual(formatted, $"{{countdown|{date:o}}}");
The first 2 closing braces are interpreted as a literal closing brace and the third as closing the formatting expression.
This is not a bug so much as a limitation of the grammar for interpolated strings. The bug, if there is one, is that the output of the formatted text should probably be "o}" instead of just "o".
The reason we have the operator "+=" instead of "=+" in C, C#, and C++ is that in the form =+ you cannot tell in some cases whether the "+" is part of the operator or a unary "+".
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With