2013-05-17 9 views
37

Versione corta:comportamento Cambiato di string.Empty (o System.String :: Vuoto) in .NET 4.5

Il codice C#

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

quando compilato ed eseguito, dà uscita "Hello world!" sotto .NET versione 4.0 e precedenti, ma fornisce "" in .NET 4.5 e .NET 4.5.1.

In che modo una scrittura su un campo può essere ignorata in questo modo oppure, chi resetta questo campo?

versione più lunga:

non ho mai veramente capito il motivo per cui il campo string.Empty (noto anche come [mscorlib]System.String::Empty) non è const (aka literal.), Vedere "Why isn't String.Empty a constant?". Questo significa che, per esempio, in C# non possiamo usare string.Empty nelle seguenti situazioni:

  • In una dichiarazione switch in forma case string.Empty:
  • come valore predefinito di un parametro opzionale, come void M(string x = string.Empty) { }
  • Quando si applica un attributo, come [SomeAttribute(string.Empty)]
  • Altre situazioni in cui è richiesta una costante della fase di compilazione

che ha implicazioni per la ben nota "guerra religiosa" sull'uso di string.Empty o "", vedere "In C#, should I use string.Empty or String.Empty or "" to intitialize a string?".

Un paio di anni fa mi sono divertito impostando Empty in un'altra istanza di stringa attraverso il riflesso e vedere quante parti del BCL iniziarono a comportarsi in modo strano a causa di esso. Ne erano parecchie. E il cambiamento del riferimento Empty sembrava persistere per tutta la vita dell'applicazione. Ora, l'altro giorno ho provato a ripetere quella piccola acrobazia, ma poi usando una macchina .NET 4.5, e non potevo più farlo.

(NB! Se si dispone di .NET 4.5 sul computer, probabilmente la PowerShell utilizza ancora una versione precedente di .NET, quindi cercate copia-incolla [String].GetField("Empty").SetValue($null, "Hello world!") in PowerShell per vedere alcuni effetti di cambiare questo riferimento.)

Quando ho provato a cercare un motivo, mi sono imbattuto nel thread interessante "What's the cause of this FatalExecutionEngineError in .NET 4.5 beta?". Nella risposta accettata a questa domanda, si nota che tramite la versione 4.0, System.String aveva un costruttore statico .cctor in cui era impostato il campo Empty (nel sorgente C#, probabilmente sarebbe solo un inizializzatore di campo, ovviamente), mentre in 4.5 non esiste un costruttore statico. In entrambe le versioni, il campo stesso ha lo stesso aspetto:

.field public static initonly string Empty 

(come visto con IL DASM).

Nessun altro campo oltre String::Empty sembra essere interessato. Ad esempio, ho sperimentato con System.Diagnostics.Debugger::DefaultCategory. Questo caso sembra analogo: una classe sigillata contenente un campo static readonly (static initonly) di tipo string. Ma in questo caso funziona bene per cambiare il valore (riferimento) attraverso la riflessione.

Tornando alla domanda:

Come è possibile, tecnicamente, che Empty non sembra cambiare (in 4.5), quando ho impostato il campo? Ho verificato che il compilatore C# non "barare" con la lettura, emette IL simile:

ldsfld  string [mscorlib]System.String::Empty 

modo che il campo reale dovrebbe essere letto.


Modifica dopo taglie è stato messo sulla mia domanda: Si noti che l'operazione di scrittura (che ha bisogno di riflessione di sicuro, dal momento che il campo è readonly (anche noto come initonly nella IL)) in realtà funziona come previsto. È il leggere operazione che è anomala. Se si legge con la riflessione, come in typeof(string).GetField("Empty").GetValue(null), tutto è normale (vale a dire il cambio di valore è visto). Vedi i commenti qui sotto.

Quindi la domanda migliore è: perché questa nuova versione del cheat del framework quando legge questo particolare campo?

+1

Potrebbe anche essere interessante sentire se questo è lo stesso con Mono. –

+0

Penso che questo sia più adatto per [Programmers] (http://programmers.stackexchange.com). – Renan

+5

Mentre il compilatore non sta barando, c'è una buona possibilità che il CLR * sia *. – dlev

risposta

3

Non ho una risposta, forse qualche indizio, forse.

L'unica differenza che vedo tra String::Empty e System.Diagnostics.Debugger::DefaultCategory è la prima contrassegnata con __DynamicallyInvokableAttribute.

Non conosco il significato di questo attributo non documentato. Una domanda su questo attributo è stata richiesta in SO: What is the __DynamicallyInvokable attribute for?

Posso solo supporre che questo attributo sia catturato dal runtime per fare un po 'di cache?

20

La differenza sta nella squadra con la nuova versione di NET, che ottimizza apparentemente riferimenti a String.Empty da inlining un riferimento ad una particolare String un'istanza anziché caricare il valore memorizzato nel campo Empty. Ciò è giustificato dalla definizione del vincolo di inizializzazione in ECMA-335 Partition I §8.6.1.2, che può essere interpretato nel senso che il valore del campo String.Empty non verrà modificato dopo l'inizializzazione della classe String.

+0

Inoltre, in .NET 4 la classe 'System.String' aveva un costruttore statico (metodo' .cctor() ') assegnato al campo. In .NET 4.5 e versioni successive, questo non è più il caso. Mi chiedo perché un read through reflection (che non sembra "barare") non veda il campo come "null" allora. –

2

Perché è possibile.

Il valore di questi campi initonly definiti dal sistema sono invarianti globali per il runtime .NET. Se questi invarianti sono interrotti, non ci sono più nessuna garanzia di alcun tipo per quanto riguarda il comportamento.

In C++, probabilmente abbiamo una regola che indica questo come causa del comportamento non definito . In .NET, è anche un comportamento indefinito, semplicemente per l'assenza di qualsiasi regola che dice cosa succede quando System.String.Empty.Length > 0. Le specifiche complete di tutti i livelli di .NET e C# descrivono il comportamento quando sono presenti anche System.String.Empty.Length == 0 e un intero gruppo di invarianti.

Per ulteriori informazioni sulle ottimizzazioni che variano tra i tempi di esecuzione e le implicazioni, vedere le risposte a