c# - not - Se modificó el comportamiento de string.Empty(o System.String:: Empty) en.NET 4.5




string or null c# (2)

La diferencia radica en el JIT para la nueva versión de .NET, que aparentemente optimiza las referencias a String.Empty al String.Empty una referencia a una instancia de String particular en lugar de cargar el valor almacenado en el campo Empty . Esto se justifica bajo la definición de la restricción de solo inicio en ECMA-335 Partición I §8.6.1.2, que puede interpretarse como que significa que el valor del campo String.Empty no cambiará después de que se inicialice la clase String .

Version corta:

El código de C #

typeof(string).GetField("Empty").SetValue(null, "Hello world!");
Console.WriteLine(string.Empty);

cuando se compila y se ejecuta, da salida "Hello world!" en .NET versión 4.0 y anterior, pero da "" en .NET 4.5 y .NET 4.5.1.

¿Cómo se puede ignorar una escritura en un campo así o quién restablece este campo?

Versión más larga:

Nunca he entendido realmente por qué el campo string.Empty (también conocido como [mscorlib]System.String::Empty ) no es const (también conocido como literal ), consulte " ¿Por qué no String.Empty es una constante? ". Esto significa que, por ejemplo, en C # no podemos usar string.Empty en las siguientes situaciones:

  • En una instrucción switch en el formulario de case string.Empty:
  • Como el valor predeterminado de un parámetro opcional, como void M(string x = string.Empty) { }
  • Al aplicar un atributo, como [SomeAttribute(string.Empty)]
  • Otras situaciones donde se requiere una constante de tiempo de compilación

que tiene implicaciones para la bien conocida "guerra religiosa" sobre si usar string.Empty o "" , ver " In C #, ¿debería usar string.Empty o String.Empty o" "para inicializar una cadena? ".

Hace un par de años me divertí al configurar Empty en alguna otra instancia de cadena mediante la reflexión, y ver cuántas partes del BCL comenzaron a comportarse de forma extraña debido a eso. Fueron muchos. Y el cambio de la referencia Empty pareció persistir durante toda la vida de la aplicación. Ahora, el otro día traté de repetir ese pequeño truco, pero luego usé una máquina .NET 4.5, y no pude hacerlo más.

(Nota: si tiene .NET 4.5 en su máquina, probablemente su PowerShell todavía use una versión anterior de .NET, intente copiar y pegar [String].GetField("Empty").SetValue($null, "Hello world!") en PowerShell para ver algunos efectos del cambio de esta referencia).

Cuando traté de buscar una razón para esto, tropecé con el interesante hilo " ¿Cuál es la causa de este FatalExecutionEngineError en .NET 4.5 beta? ". En la respuesta aceptada a esa pregunta, se observa que a través de la versión 4.0, System.String tenía un constructor estático .cctor en el que se establecía el campo Empty (en la fuente C #, que probablemente solo sería un inicializador de campo, por supuesto) , mientras que en 4.5 no existe ningún constructor estático. En ambas versiones, el campo en sí tiene el mismo aspecto:

.field public static initonly string Empty

(como se vio con IL DASM).

No hay otros campos que String::Empty que se vean afectados. Como ejemplo, experimenté con System.Diagnostics.Debugger::DefaultCategory . Este caso parece análogo: una clase sellada que contiene un campo static readonly ( static initonly ) de tipo string . Pero en este caso, funciona bien cambiar el valor (referencia) a través de la reflexión.

Volver a la pregunta:

¿Cómo es posible, técnicamente, que Empty no parezca cambiar (en 4.5) cuando configuro el campo? He verificado que el compilador de C # no "engaña" con la lectura, emite IL como:

ldsfld     string [mscorlib]System.String::Empty

entonces el campo real debería ser leído.

Editar después de que la bonanza se haya puesto en mi pregunta: initonly en cuenta que la operación de escritura (que necesita una reflexión segura, ya que el campo es de readonly (también initonly como initonly en IL)) realmente funciona como se esperaba. Es la operación de lectura que es anómala. Si lee con reflexión, como en typeof(string).GetField("Empty").GetValue(null) , todo es normal (es decir, se ve el cambio de valor). Ver comentarios a continuación.

Entonces, la mejor pregunta es: ¿por qué esta nueva versión del marco engaña cuando lee este campo en particular?


No tengo una respuesta, juste alguna pista, tal vez.

La única diferencia que veo entre String::Empty y System.Diagnostics.Debugger::DefaultCategory es la primera que está etiquetada con __DynamicallyInvokableAttribute .

No sé el significado de este atributo no documentado. Se ha formulado una pregunta sobre este atributo en SO: ¿Para qué sirve el atributo __DynamicallyInvokable?

¿Solo puedo suponer que este atributo es capturado por el tiempo de ejecución para hacer algo de almacenamiento en caché?





string