Problemas de interpolación de cadenas


Estoy tratando de averiguar por qué mi prueba unitaria falla (La tercera afirmación a continuación):

var date = new DateTime(2017, 1, 1, 1, 0, 0);

var formatted = "{countdown|" + date.ToString("o") + "}";

//Works
Assert.AreEqual(date.ToString("o"), $"{date:o}");
//Works
Assert.AreEqual(formatted, $"{{countdown|{date.ToString("o")}}}");
//This one fails
Assert.AreEqual(formatted, $"{{countdown|{date:o}}}");

AFAIK, esto debería funcionar correctamente, pero parece que no pasa el parámetro de formato correctamente, aparece como solo {countdown|o} al código. ¿Alguna idea de por qué esto está fallando?

Author: FrankerZ, 2017-02-09

4 answers

El problema con esta línea

Assert.AreEqual(formatted, $"{{countdown|{date:o}}}");

Es que tiene 3 comillas rizadas después de format string de la variable a escapar y comienza a escapar de izquierda a derecha, por lo tanto, trata las primeras 2 comillas rizadas como parte de la cadena de formato y la tercera comilla rizada como la de cierre.

Así que transforma o en o} y no puede interpolarlo.

Esto debería funcionar

Assert.AreEqual(formatted, $"{{countdown|{date:o}"+"}");

Observe que el $"{date}}}" más simple (es decir, 3 rizos después de la variable sin a format string) funciona porque reconoce que la primera comilla rizada es la de cierre, mientras que la interpretación del especificador de formato después de la : rompe la correcta identificación de paréntesis de cierre.

Para probar que la cadena de formato es escapada como una cadena, considere que la siguiente

$"{date:\x6f}"

Se trata como

$"{date:o}"

Finalmente, es perfectamente posible que las comillas rizadas de doble escape sean parte de una costumbre formato de fecha, por lo que es absolutamente razonable el comportamiento del compilador. Una vez más, un ejemplo concreto

$"{date:MMM}}dd}}yyy}" // it's a valid feb}09}2017

El análisis es un proceso formal basado en reglas gramaticales de expresión, no se puede hacer simplemente echándole un vistazo.

 21
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-09 20:28:00

Esto es un seguimiento a mi respuesta original en orden

Para asegurarse de que este es el comportamiento previsto

En lo que respecta a una fuente oficial, debemos referirnos a las cadenas interpoladas de msdn.

La estructura de una cadena interpolada es

$ " <text> { <interpolation-expression> <optional-comma-field-width> <optional-colon-format> } <text> ... } "  

Y cada interpolación individual se define formalmente con una sintaxis

single-interpolation:  
    interpolation-start  
    interpolation-start : regular-string-literal  

interpolation-start:  
    expression  
    expression , expression  

Lo que cuenta aquí es que

  1. el optional-colon-format se define como un regular-string-literal syntax = > es decir, puede contener un escape-sequence, de acuerdo con el paragraph 2.4.4.5 String literals de la Especificación del Lenguaje C # 5.0
  2. Puede usar una cadena interpolada en cualquier lugar puede usar un string literal
  3. Para incluir una llave rizada ({ o }) en una cadena interpolada use dos llaves, {{ o }} => es decir, el compilador escapa dos llaves en el optional-colon-format
  4. el compilador escanea la interpolación contenida expressions como texto balanceado hasta que encuentra una coma, colon, o cerrar la llave => es decir, dos puntos rompe el texto equilibrado así como un cierre de llave

Solo para ser claros, esto explica la diferencia entre $"{{{date}}}" donde date es un expression y por lo tanto se tokeniza hasta el primer corsé rizado versus $"{{{date:o}}}" donde date es de nuevo un expression y ahora se tokeniza hasta los primeros dos puntos, después de lo cual comienza un literal de cadena regular y el compilador reanuda escapando dos llaves, sucesivamente...

También está el String Formatting FAQ de msdn, donde este caso se trató explícitamente.

int i = 42;
string s = String.Format(“{{{0:N}}}”, i);   //prints ‘{N}’

La pregunta es, ¿por qué falló este último intento? Hay dos cosas usted necesita saber para entender este resultado:

Cuando se proporciona un especificador de formato, el formato de cadena toma estos pasos:

Determine si el especificador es más largo que un solo carácter: si es así, a continuación, suponga que el especificador es una costumbre formato. Un formato personalizado utilizará reemplazos adecuados para su formato, pero si no sabe qué hacer con algún carácter, simplemente lo escribirá como un literal encontrado en el formato Determinar si el carácter único especificador es un especificador compatible (como ' N ' para número formatear). Si lo es, formatee apropiadamente. Si no, lanza un ArgumnetException

Al intentar determinar si un corchete rizado debe ser escaped, los soportes rizados se tratan simplemente en el orden que son recibir. Por lo tanto, {{{ escapará los dos primeros caracteres y imprima el literal {, y el tercer corchete rizado comenzará el sección de formato. Sobre esta base, en }}} los dos primeros rizados los corchetes se escaparán, por lo tanto un literal } se escribirá a la cadena de formato, y luego se asumirá que el último corchete rizado estar terminando una sección de formato Con esta información, ahora podemos averiguar lo que está ocurriendo en nuestra situación {{{0:N}}}. La primera se escapan dos corchetes rizados, y luego tenemos una sección de formato. Sin embargo, luego también escapamos del soporte rizado de cierre, antes de cerrar la sección de formato. Por lo tanto, nuestra sección de formato es en realidad interpretado como conteniendo 0:N}. Ahora, el formateador mira la especificador de formato y ve N} para el especificador. Por lo tanto, interpreta esto como un formato personalizado, y ya que ni N ni } significan cualquier cosa para un formato numérico personalizado, estos caracteres son simplemente escrito, en lugar del valor de la variable referenciada.

 6
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-12 03:19:44

El problema parece ser que para insertar un paréntesis mientras se usa la interpolación de cadenas, debe escapar duplicándolo. Si agregamos el paréntesis utilizado para la interpolación en sí, terminamos con un triple paréntesis como el que tenemos en la línea que nos da la excepción:

Assert.AreEqual(formatted, $"{{countdown|{date:o}}}");

Ahora, si observamos la "}}}", podemos notar que el primer paréntesis encierra la interpolación de cadena, mientras que los dos últimos están destinados a ser tratados como una cadena-escapado carácter de paréntesis.

El compilador, sin embargo, está tratando los dos primeros como el carácter de cadena scaped, por lo que está insertando una cadena entre los delimitadores de interpolación. Básicamente el compilador está haciendo algo como esto:

string str = "a string";
$"{str'}'}"; //this would obviously generate a compile error which is bypassed by this bug

Puede resolver esto reformateando la línea como tal:

Assert.AreEqual(formatted, $"{{countdown|{$"{date:o}"}}}");
 2
Author: Innat3,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-09 17:00:50

Esta es la forma más fácil de hacer que la afirmación funcione...

Assert.AreEqual(formatted, "{" + $"countdown|{date:o}" + "}");

En esta forma...

Assert.AreEqual(formatted, $"{{countdown|{date:o}}}");

Las primeras 2 llaves de cierre se interpretan como una llave de cierre literal y la tercera como cierre de la expresión de formato.

Esto no es un error sino una limitación de la gramática para cadenas interpoladas. El error, si hay uno, es que la salida del texto formateado probablemente debería ser "o}" en lugar de solo "o".

La razón por la que tenemos el operador "+ = " en su lugar de "= + "en C, C#, y C++ es que en la forma =+ no se puede decir en algunos casos si el" + "es parte del operador o un único"+".

 1
Author: AQuirky,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-17 21:45:22