-
Notifications
You must be signed in to change notification settings - Fork 4.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Bug] Interpolated strings in attributes #9407
Comments
I don't think this is a bug, the string interpolation is seen as a function call. Wither it consists of constants only arguments, has no barring on how is treated by the compiler. |
Followig those proposals, I think a culture independent form of string interpolation might be useful in these situations when a simple concat is enough. |
And that was the first method relating to string interpolation I added to my LinqPad extensions:
But it's still clearly not a constant expression. |
I can't agree that this case isn't an unexpected behavior. The string interpolation syntax looks like strings. You expect similar behavior when you write "Field 'FirstName' is required." or $"Field 'FirstName' is required.". I understand that the interpolated string $"..." converts to String.Format: static void Test_nameof(String name)
{
Console.WriteLine($"Test_nameof: {name}");
}
static void Test_CallerMemberName([CallerMemberName] String name = "")
{
Console.WriteLine($"{nameof(Test_CallerMemberName)}: {name}");
} The code converted by compiler to following code: static void Test_nameof(String name)
{
Console.WriteLine(string.Format("Test_nameof: {0}", name));
}
static void Test_CallerMemberName([CallerMemberName] String name = "")
{
Console.WriteLine(string.Format("{0}: {1}", "Test_CallerMemberName", name));
} I test it by ILSpy. It may be added the following rules to string interpolation: $"'\@{MyNamespace.SomType}', '\@{SomType arg0}', '\@{arr[index]}'."
//'MyNamespace.SomeType', 'SomType arg0', 'arr[index]'.
$"'\@{MyNamespace.{SomeType}}', '\@{{SomType} arg0}', '\@{{public }int {Prop}}'."
//'SomeType', 'SomeType', 'public Prop'. The braces used for the selective conversion There is no opportunity to transform part of a code to string by principle “as is” to track its further changes: “System.Math.PI”, “Type Property” or “Type Namespace.Class.Method(Type arg0, Type arg1, ...)”. Functional style of the operator is perhaps doubtful. I'd want to ask a question: what type of argument? But the question disappears, because already there are similar methods like “typeof”. In addition, after compilation the method-like operator is replaced by string. Perhaps, it would be more natural to expand functionality of the string interpolation for strict and literally transform parts of code to string with validating it at compile time. // It's similar preprocessor directive (#) for interpolation string. So It acts at compile time
$" The property name is '\#nameof{PropertyName}'."
// After compilation: " The property name is 'PropertyName'."
$" The constant expression evaluated at compile time is '\#eval{2+2}'."
// After compilation: " The constant expression evaluated at compile time is '4'."
$" The constant expression evaluated at compile time is \#lang{ru-RU}'\#eval{(2+2)/8}' \#lang{invariant}'\#eval{(2+2)/8}'\#lang{default}."
// After compilation: " The constant expression evaluated at compile time is '0,5' '0.5'."
$" The format provider may be changed by \#format{0.00, MyCustomProvider}{myFloatVariable + 0.5}\#format{}"
// After compilation: String.Format(" The format provider may be changed by {0}", (myFloatVariable + 0.5).ToString("0.00", MyCustomProvider)) By the way, if it would be added the #{} to the ordinal strings, then there is no need to write a dollar sign at the beginning of interpolation string: "\#lang{ru-RU} '\#codeof{MyClass.myStaticFloatVariable + 0.5}' = \#{myStaticFloatVariable + 0.5}, \#eval{myConstantOf5 + 0.5}"
// After compilation: String.Format(new CultureInfo( "ru-RU" ), " 'MyClass.myStaticFloatVariable + 0.5' = {0}, 5,5", (myStaticFloatVariable + 0.5))
// or
// String.Format(" 'MyClass.myStaticFloatVariable + 0.5' = {0}, 5,5", (myStaticFloatVariable + 0.5).ToString(new CultureInfo( "ru-RU" )))
// The compiler can optimize the CultureInfo instance places during compilation In this case, the suggested behavior of the interpolated strings becomes more clear. |
@p-e-timoshenko |
Thanks for taking the time to report this issue. Unfortunately this behavior is by design. Interpolated strings involve function calls under the hood and hence are not constant values. This means they can't be included as attribute values or assigned to |
@alrz that is not a constant string either. The string resulting from |
That's insensitive to my Pig Latin heritage. 😦 Seriously, though, I do think that @alrz 's argument is that a double |
@gafter but |
That is why we have string interpolation, which one do you prefer "TestMethod: " + nameof(TestMethod) + " Whatever: " + whatever;
$"TestMethod: {nameof(TestMethod)} Whatever: {whatever}";
In fact, I rarely use all the features of string interpolation like |
If the target is |
@AdamSpeight2008 The problem is that there is an implicit argument to string interpolation, which is the current culture. We cannot enforce that the current culture is a constant, because it isn't a constant. |
@gafter, I believe he is implying that the where Edit -or, rather |
The compiler can't assume anything about the implementation of string.Format, especially not that the platform it's running on is the same as the program it targets and thus the implementations of string.Format are the same. |
I don't know what is the problem with translating interpolation to a bunch of string concatenation operators at the interpolation sites, in that case you may not be able to use culture dependant features like |
@alrz Interpolation is always culture dependency because it always uses the current culture to format, even if you're only interpolating strings. Technically the culture could intercept and rewrite those strings to whatever value it wants, regardless of format specifier or other options. You'd need a new form of interpolation which is explicitly designed to concatenate or something of that nature. Or you'd need a built-in interpolation prefix, perhaps. |
Still all the options above seem promising,
The thing is that if the user want a constant, he/she woudn't care what the culture is, and probably woudn't want to use culture dependant features. That's why you may be forced to use string concat in these cases for the sake of constness of the resultant string. @tmat The compiler wouldn't assume anything about the implementation of |
Why can't the assumed culture at compile-time be |
@AdamSpeight2008 The defined semantics of an interpolated string is that they use the culture at runtime, not at compile-time. |
@tannergooding @AdamSpeight2008 The ordinal strings is defined by quotes - "". The backslash means special character named escape sequence (https://msdn.microsoft.com/library/h21280bw(v=vs.110).aspx). String.Format($"{0}", 1); // Output: 0
String.Format($"{{0}}", 1); // Output: 1
String.Format($"{{{{0}}}}", 1); // Output: {0} Let's see what will be if the dollar sign is absent. "..." // ordinary string
// After compilation: "..."
"...\n\t..." // ordinary string with escape characters
// After compilation: "...\n\t..."
"...\#..." // Error: Unrecoginzed escape sequence
// In C ++ the hash symbol associated with preprocessor directives.
// Therefore, it can be added to strings as string preprocessor directives.
// \#simple-string-preprocessor-directive# or \#string-preprocessor-directive-with_argument{}
// RegEx: \\#([A-Za-z][A-Za-z0-9-_]*(?=#|{|[^A-Za-z0-9-_]))#?(\{([^{}]|(?R))*\})?
" The property name is '\#nameof{PropertyName}'."
// After compilation: " The property name is 'PropertyName'."
// Older versions of .NET will generate an error: Unrecoginzed escape sequence
" The property value is '\#eval{1f/2f + 1f/3f}'."
// After compilation: " The property value is '0.8333333'."
"\#culture{ru-RU} The property value is '\#eval{1f/2f + 1f/3f}'."
// After compilation: " The property value is '0,8333333'."
"The property value is '\#culture{ru-RU}\#eval{1f/2f + 1f/3f}'."
// After compilation: " The property value is '0,8333333'."
// the directive "culture" affects to the next directive only if it is written in succession \#dir1\#dir2
// The directive "eval" tries to convert to string at compile time.
// What couldn't be converted, it makes the argument of String.Format
"The property value is '\#culture{ru-RU}\#eval{1f/2f + 1f/3f + someVar}'."
// After compilation: String.Format(" The property value is '{0}'.", (1f/2f + 1f/3f + someVar).ToString(new CultureInfo("ru-RU")))
// You can forcibly to make value as a parameter of String.Format:
"The property value is '\#culture{ru-RU}\#insert{1f/2f + 1f/3f + someConst}'."
// After compilation: String.Format(" The property value is '{0}'.", (1f/2f + 1f/3f + someConst).ToString(new CultureInfo("ru-RU")))
"The algebraic expression '\#codeof{0.5f + 1f/3f}' equals '\#culture{ru-RU}\#eval{1f/2f + 1f/3f}'."
// After compilation: " The algebraic expression '0.5f + 1f/3f' equals '0,8333333'." |
@p-e-timoshenko Sorry, the ship has sailed regarding the syntax and semantics for string interpolation. Those debates all played out on CodePlex. Notably the original syntax did not use a prefix and used a form of escape sequence within normal strings, and the consensus was that this was not a good idea. |
@HaloFour _ Of course, the escaping braces ("{variable}") is difficult to read. For example "Hello {person.name}, you were born on {person.dob:D}. {{person.age} years ago!}". How difficult is it to read my examples? |
Quite, but it doesn't matter because string interpolation already shipped, the syntax won't be subject to breaking change, and you'd have to demonstrate a really big reason why another form of interpolation is worth implementing. |
I think @jods4's argument in #9212 could be "a really big reason" to introduce that kind of interpolation in the language and it doesn't interfere with anything that already been shipped.
Preserving constness is another "big reason" for doing this. To not introduce another form I'd be ok with context-sensitive translation e.g. allowing interpolation for |
Possibly, but it's not like any of these limitations are a surprise. The issues of culture and performance were all already debated and C# 6 shipped with the result of those conversations. String interpolation was not designed to fit the scenarios described here, and that was intentional. |
I think this issue being filed as a "bug" is consequence of a surprise.
The culture dependance of string interpolation is totally understandable but inability to use it in other contexts when it makes sense is not. It's like
I'm saying that you should be able to change the "old style" to new idomatics transparently, for example [Required(ErrorMessage = "Field 'FirstName' is required.")]
// is equivalent to
[Required(ErrorMessage = "Field '" + nameof(FirstName) + "' is required.")] But suddenly [Required(ErrorMessage = $"Field '{nameof(FirstName)}' is required.")] // ERROR!!! |
I meant by the design team, who have already closed this issue since this behavior is by design. I'm not disagreeing (or agreeing) with these arguments, I'm only pointing out that they were already made while this feature was still being designed. The syntax and behavior changed several times due to community feedback. What shipped is the result from those collaborations same last year. I'd be pretty shocked if the team were willing to revisit those same arguments or to consider another syntax for interpolation. But weirder things have probably happened. |
Version Used: CS 6
Steps to Reproduce:
The text was updated successfully, but these errors were encountered: