Perché il valore predefinito del tipo di stringa è nullo anziché una stringa vuota?


218

È abbastanza fastidioso testare tutte le mie stringhe nullprima di poter applicare in modo sicuro metodi come ToUpper(), StartWith()ecc.

Se il valore predefinito di stringfosse la stringa vuota, non dovrei testarlo e lo riterrei più coerente con gli altri tipi di valore come into doublead esempio. Inoltre Nullable<String>avrebbe senso.

Quindi perché i progettisti di C # hanno scelto di utilizzare nullcome valore predefinito le stringhe?

Nota: si riferisce a questa domanda , ma si concentra maggiormente sul perché anziché su cosa farne.


53
Consideri questo un problema per altri tipi di riferimento?
Jon Skeet,

17
@JonSkeet No, ma solo perché inizialmente, erroneamente, pensavo che le stringhe fossero tipi di valore.
Marcel,

21
@Marcel: questa è una buona ragione per chiedertelo.
TJ Crowder,

7
@JonSkeet Sì. Oh si. (Ma non sei estraneo alla discussione sul tipo di riferimento non nullable ...)
Konrad Rudolph,

7
Credo che avresti un tempo molto migliore se usassi delle asserzioni sulle tue corde in luoghi dove non ti aspetti che siano null(e inoltre ti consiglio di trattare concettualmente nulle svuotare le stringhe come cose diverse). Un valore nullo potrebbe essere il risultato di un errore da qualche parte, mentre una stringa vuota dovrebbe trasmettere un significato diverso.
Diegoreymendez,

Risposte:


312

Perché il valore predefinito del tipo di stringa è nullo anziché una stringa vuota?

Perché stringè un tipo di riferimento e il valore predefinito per tutti i tipi di riferimento è null.

È abbastanza fastidioso testare tutte le mie stringhe per null prima di poter applicare in modo sicuro metodi come ToUpper (), StartWith () ecc ...

Ciò è coerente con il comportamento dei tipi di riferimento. Prima di invocare i loro membri di istanza, si dovrebbe mettere in atto un segno di spunta per un riferimento null.

Se il valore predefinito di stringa fosse la stringa vuota, non avrei dovuto testarlo e lo riterrei più coerente con gli altri tipi di valore come int o double, ad esempio.

Assegnare il valore predefinito a un tipo di riferimento specifico diverso da quello nulllo renderebbe incoerente .

Inoltre Nullable<String>avrebbe senso.

Nullable<T>funziona con i tipi di valore. Da notare il fatto che Nullablenon è stato introdotto sulla piattaforma .NET originale , quindi ci sarebbe stato un sacco di codice non funzionante se avessero cambiato quella regola. ( Courtesy @jcolebrand )


10
@HenkHolterman Uno potrebbe implementare un sacco di cose, ma perché introdurre un'incoerenza così evidente?

4
@delnan - "why" era la domanda qui.
Henk Holterman,

8
@HenkHolterman E "Coerenza" è la confutazione al tuo punto "la stringa potrebbe essere trattata diversamente da altri tipi di riferimento".

6
@delnan: Lavorando su una lingua che tratta le stringhe come tipi di valore e lavorando su oltre 2 anni su dotnet, sono d'accordo con Henk. Lo vedo come un FLAW importante su dotnet.
Fabricio Araujo,

1
@delnan: si potrebbe creare un tipo di valore che si è comportato essenzialmente come String, ad eccezione di (1) il comportamento valore-tipo-ish di avere un valore predefinito utilizzabile e (2) uno sfortunato livello aggiuntivo di indiretta boxe ogni volta che è stato lanciato Object. Dato che la rappresentazione dell'heap di stringè unica, avere un trattamento speciale per evitare boxe extra non sarebbe stato un grande sforzo (in realtà, essere in grado di specificare comportamenti di boxe non predefiniti sarebbe una buona cosa anche per altri tipi).
supercat

40

Habib ha ragione, perché stringè un tipo di riferimento.

Ma soprattutto, non è necessario controllare nullogni volta che lo si utilizza. Tuttavia, dovresti probabilmente lanciare un ArgumentNullExceptionse qualcuno passa la tua funzione come nullriferimento.

Ecco la cosa: il framework NullReferenceExceptionti lancerebbe comunque per te se provassi a chiamare .ToUpper()una stringa. Ricorda che questo caso può ancora verificarsi anche se testi i tuoi argomenti nullpoiché qualsiasi proprietà o metodo sugli oggetti passa alla tua funzione come i parametri possono valutare null.

Detto questo, controllare stringhe vuote o null è una cosa comune da fare, quindi forniscono String.IsNullOrEmpty()e String.IsNullOrWhiteSpace()solo per questo scopo.


30
Non dovresti mai lanciartiNullReferenceException ( msdn.microsoft.com/en-us/library/ms173163.aspx ); si genera un ArgumentNullExceptionse il metodo non può accettare riferimenti null. Inoltre, NullRef è in genere una delle eccezioni più difficili alla diagnostica quando si risolvono i problemi, quindi non credo che la raccomandazione di non verificare la presenza di null sia molto valida.
Andy,

3
@Andy "NullRef è in genere una delle eccezioni più difficili da diagnosticare" Non sono assolutamente d'accordo, se si registra qualcosa è davvero facile da trovare e risolvere (basta gestire il caso null).
Louis Kottmann,

6
Il lancio ArgumentNullExceptionha l'ulteriore vantaggio di poter fornire il nome del parametro. Durante il debug, questo fa risparmiare ... err, secondi. Ma secondi importanti.
Kos,

2
@DaveMarkle si consiglia di includere IsNullOrWhitespace troppo msdn.microsoft.com/en-us/library/...
Nathan Koop

1
Penso davvero che il controllo di null ovunque sia una fonte di immenso codice gonfio. è brutto e sembra confuso ed è difficile rimanere coerenti. Penso che (almeno nei linguaggi simili a C #) una buona regola è "vietare la parola chiave null nel codice di produzione, usarla come un matto nel codice di prova".
Sara

24

Puoi scrivere un metodo di estensione (per quello che vale):

public static string EmptyNull(this string str)
{
    return str ?? "";
}

Ora funziona in modo sicuro:

string str = null;
string upper = str.EmptyNull().ToUpper();

100
Ma per favore no. L'ultima cosa che un altro programmatore vuole vedere sono migliaia di righe di codice piene di .EmptyNull () ovunque solo perché il primo ragazzo era "spaventato" dalle eccezioni.
Dave Markle,

15
@DaveMarkle: Ma ovviamente è esattamente ciò che OP stava cercando. "È abbastanza fastidioso testare tutte le mie stringhe per null prima di poter applicare in modo sicuro metodi come ToUpper (), StartWith () ecc."
Tim Schmelter,

19
Il commento era all'OP, non a te. Mentre la tua risposta è chiaramente corretta, un programmatore che pone una domanda di base come questa dovrebbe essere fortemente avvertito di non mettere effettivamente la tua soluzione nella pratica WIDE, come spesso accade. Esistono numerosi compromessi di cui non si discute nella risposta, come opacità, maggiore complessità, difficoltà di refactoring, potenziale uso eccessivo di metodi di estensione e sì, prestazioni. A volte (molte volte) una risposta corretta non è la strada giusta, ed è per questo che ho commentato.
Dave Markle,

5
@Andy: la soluzione per non eseguire correttamente il controllo null è quella di verificare correttamente la presenza di null, non di mettere un cerotto su un problema.
Dave Markle,

7
Se stai attraversando il problema della scrittura .EmptyNull(), perché non semplicemente usare (str ?? "")invece dove è necessario? Detto questo, sono d'accordo con il sentimento espresso nel commento di @ DaveMarkle: probabilmente non dovresti. nulle String.Emptysono concettualmente diversi e non puoi necessariamente trattarli come gli altri.
un CVn il

17

È inoltre possibile utilizzare quanto segue, a partire da C # 6.0

string myString = null;
string result = myString?.ToUpper();

Il risultato della stringa sarà null.


1
Per essere corretti, poiché c # 6.0, la versione dell'IDE non ha nulla a che fare con esso poiché questa è una funzione del linguaggio.
Stijn Van Antwerpen,

3
Un'altra opzione -public string Name { get; set; } = string.Empty;
Jaja Harris,

Come si chiama? ? myString .ToUpper ();
Hunter Nelson,

1
Si chiama operatore Null-Conditional. Si può leggere su qui msdn.microsoft.com/en-us/magazine/dn802602.aspx
russelrillema

14

Stringhe e null vuoti sono fondamentalmente diversi. Un null è un'assenza di un valore e una stringa vuota è un valore che è vuoto.

Il linguaggio di programmazione che fa assunzioni sul "valore" di una variabile, in questo caso una stringa vuota, sarà buono come avviare la stringa con qualsiasi altro valore che non causerà un problema di riferimento null.

Inoltre, se si passa l'handle a quella variabile di stringa ad altre parti dell'applicazione, quel codice non avrà modo di convalidare se si è passati intenzionalmente un valore vuoto o si è dimenticato di popolare il valore di quella variabile.

Un'altra occasione in cui questo sarebbe un problema è quando la stringa è un valore di ritorno da una funzione. Poiché la stringa è un tipo di riferimento e può tecnicamente avere un valore come nullo e vuoto entrambi, quindi la funzione può anche tecnicamente restituire un valore nullo o vuoto (non c'è nulla che gli impedisca di farlo). Ora, poiché ci sono 2 nozioni di "assenza di un valore", cioè una stringa vuota e un valore nullo, tutto il codice che utilizza questa funzione dovrà fare 2 controlli. Uno per vuoto e l'altro per null.

In breve, è sempre bene avere solo 1 rappresentazione per un singolo stato. Per una discussione più ampia su vuoti e null, vedere i collegamenti seguenti.

/software/32578/sql-empty-string-vs-null-value

NULL vs Empty quando si ha a che fare con l'input dell'utente


2
E come vedi esattamente questa differenza, ad esempio in una casella di testo? L'utente ha dimenticato di inserire un valore nel campo o lo ha lasciato intenzionalmente vuoto? Nulla in un linguaggio di programmazione ha un significato specifico; non assegnato. Sappiamo che non ha un valore, che non è lo stesso di un database null.
Andy,

1
non c'è molta differenza quando lo si utilizza con una casella di testo. In entrambi i casi, avere una notazione per rappresentare l'assenza di un valore in una stringa è fondamentale. Se dovessi sceglierne uno, sceglierei null.
Nerrve,

In Delphi, string è un tipo di valore e quindi non può essere nullo. Rende la vita molto più semplice in questo senso - trovo davvero fastidioso rendere la stringa un tipo di riferimento.
Fabricio Araujo,

1
Sotto il COM (Common Object Model) che ha preceduto .net, un tipo di stringa può contenere un puntatore ai dati della stringa o nullrappresentare la stringa vuota. Esistono diversi modi in cui .net avrebbe potuto implementare una semantica simile, se avessero scelto di farlo, specialmente dato che Stringha un certo numero di caratteristiche che lo rendono comunque un tipo unico [es. Esso e i due tipi di array sono gli unici tipi la cui allocazione la dimensione non è costante].
supercat

7

Il motivo / problema fondamentale è che i progettisti della specifica CLS (che definisce il modo in cui le lingue interagiscono con .net) non hanno definito un mezzo attraverso il quale i membri della classe potevano specificare che dovevano essere chiamati direttamente, piuttosto che via callvirt, senza che il chiamante eseguisse un controllo di riferimento null; né forniva una cattiva definizione di strutture che non sarebbero soggette al "normale" pugilato.

Se la specifica CLS avesse definito tali mezzi, sarebbe possibile che .net seguisse costantemente il lead stabilito dal COM (Common Object Model), in base al quale un riferimento a stringa null era considerato semanticamente equivalente a una stringa vuota, e per altri tipi di classe immutabili definiti dall'utente che dovrebbero avere una semantica di valore per definire allo stesso modo valori predefiniti. In sostanza, ciò che accadrebbe sarebbe per ogni membro di String, ad esempio, Lengthessere scritto come qualcosa del genere [InvokableOnNull()] int String Length { get { if (this==null) return 0; else return _Length;} }. Questo approccio avrebbe offerto una semantica molto piacevole per cose che dovrebbero comportarsi come valori, ma a causa di problemi di implementazione devono essere archiviati nell'heap. La più grande difficoltà con questo approccio è che la semantica della conversione tra tali tipi e Objectpotrebbe diventare un po 'oscura.

Un approccio alternativo sarebbe stato quello di consentire la definizione di tipi di strutture speciali che non ereditavano Objectma che avevano invece operazioni personalizzate di boxing e unboxing (che sarebbero state convertite in / da qualche altro tipo di classe). In base a tale approccio, ci sarebbe un tipo di classe NullableStringche si comporta come stringa ora, e un tipo di struttura personalizzato String, che conterrebbe un singolo campo privato Valuedi tipo String. Tentare di convertire Stringin NullableStringo Objectrestituirebbe Valuese non nullo o String.Emptyse nullo. Tentando di eseguire il cast String, un riferimento non nullo a NullableStringun'istanza memorizzerebbe il riferimento in Value(forse memorizzando null se la lunghezza era zero); lanciare qualsiasi altro riferimento genererebbe un'eccezione.

Anche se le stringhe devono essere archiviate nell'heap, concettualmente non c'è motivo per cui non debbano comportarsi come tipi di valore che hanno un valore predefinito non nullo. La loro memorizzazione come struttura "normale" che conteneva un riferimento sarebbe stata efficace per il codice che li utilizzava come tipo "stringa", ma avrebbe aggiunto un ulteriore livello di indiretto e inefficienza quando si lanciava su "oggetto". Anche se non prevedo che .net aggiungerà una delle funzionalità di cui sopra in questa data avanzata, forse i progettisti di framework futuri potrebbero prendere in considerazione l'idea di includerle.


1
Parlando come qualcuno che lavora molto in SQL e ha affrontato il mal di testa di Oracle che non faceva una distinzione tra NULL e zero-length, sono molto contento che .NET lo faccia . "Vuoto" è un valore, "null" non lo è.

@JonofAllTrades: Non sono d'accordo. Sul codice dell'applicazione, ad eccezione del codice db, non ha senso una stringa trattata come una classe. È un tipo di valore e uno di base. Supercat: +1 a te
Fabricio Araujo,

1
Il codice del database è un grande "tranne". Finché ci sono alcuni domini problematici in cui è necessario distinguere tra "presente / conosciuto, una stringa vuota" e "non presente / sconosciuto / inapplicabile", come i database, la lingua deve supportarlo. Naturalmente ora che .NET ha Nullable<>, le stringhe potrebbero essere reimplementate come tipi di valore; Non posso parlare dei costi e dei benefici di una tale scelta.

3
@JonofAllTrades: il codice che si occupa dei numeri deve avere un mezzo fuori banda per distinguere il valore predefinito zero da "non definito". Allo stato attuale, il codice di gestione nullable che funziona con stringhe e numeri deve usare un metodo per stringhe nullable e un altro per numeri nullable. Anche se un tipo di classe nullable stringè più efficiente di quanto Nullable<string>sarebbe, dover utilizzare il metodo "più efficiente" è più oneroso che poter utilizzare lo stesso approccio per tutti i valori del database di dati nullable.
supercat

5

Perché una variabile stringa è un riferimento , non un'istanza .

Inizializzarlo su Vuoto per impostazione predefinita sarebbe stato possibile, ma avrebbe introdotto molte incongruenze su tutta la linea.


3
Non vi è alcun motivo particolare che stringdovrebbe essere un tipo di riferimento. A dire il vero, i caratteri effettivi che compongono la stringa devono sicuramente essere memorizzati nell'heap, ma data la quantità di supporto dedicato che le stringhe hanno già nel CLR, non sarebbe un tratto avere System.Stringun tipo di valore con un unico campo privato Valuedi tipo HeapString. Quel campo sarebbe un tipo di riferimento e sarebbe predefinito null, ma una Stringstruttura il cui Valuecampo era null si comporterebbe come una stringa vuota. L'unico svantaggio di questo approccio sarebbe ...
supercat

1
... che lanciare un Stringto Object, in assenza di un codice di caso speciale in fase di esecuzione, causerebbe la creazione di Stringun'istanza in box sull'heap, anziché semplicemente copiare un riferimento a HeapString.
supercat

1
@supercat - nessuno dice che la stringa dovrebbe / potrebbe essere un tipo di valore.
Henk Holterman,

1
Nessuno tranne me. Avere la stringa come tipo di valore "speciale" (con un campo di tipo di riferimento privato) consentirebbe a gran parte della gestione di essere sostanzialmente efficiente come lo è ora, ad eccezione di un controllo null aggiunto su metodi / proprietà come .Lengthecc. In modo che le istanze che contengono un riferimento null non tenterebbe di dereferenziarlo, ma si comporterebbe nel modo appropriato per una stringa vuota. Se il Framework sarebbe meglio o peggio se stringimplementato in quel modo, se si volesse default(string)essere una stringa vuota ...
supercat

1
... stringessere un wrapper del tipo di valore in un campo del tipo di riferimento sarebbe l'approccio che richiedeva il minor numero di modifiche ad altre parti di .net [in effetti, se si fosse disposti ad accettare la conversione da Stringper Objectcreare un ulteriore riquadro, si potrebbe semplicemente Stringessere una struttura ordinaria con un campo di tipo Char[]che non ha mai esposto]. Penso che avere un HeapStringtipo sarebbe probabilmente migliore, ma per certi versi la stringa del tipo di valore che contiene un Char[]sarebbe più semplice.
supercat

5

Perché i progettisti di C # hanno scelto di utilizzare null come valore predefinito delle stringhe?

Poiché le stringhe sono tipi di riferimento , i tipi di riferimento sono il valore predefinito null. Le variabili dei tipi di riferimento memorizzano i riferimenti ai dati effettivi.

Usiamo la defaultparola chiave per questo caso;

string str = default(string); 

strè un string, quindi è un tipo di riferimento , quindi il valore predefinito è null.

int str = (default)(int);

strè un int, quindi è un tipo di valore , quindi il valore predefinito è zero.


4

Se il valore predefinito di stringfosse la stringa vuota, non avrei dovuto testare

Sbagliato! La modifica del valore predefinito non cambia il fatto che si tratta di un tipo di riferimento e qualcuno può comunque impostare esplicitamente il riferimento su null.

Inoltre Nullable<String>avrebbe senso.

Vero punto Avrebbe più senso non consentire nullalcun tipo di riferimento, invece richiederebbe Nullable<TheRefType>quella funzione.

Quindi perché i progettisti di C # hanno scelto di utilizzare nullcome valore predefinito le stringhe?

Coerenza con altri tipi di riferimento. Ora, perché consentire nulla tutti i tipi di riferimento? Probabilmente così sembra C, anche se questa è una decisione di progettazione discutibile in un linguaggio che fornisce anche Nullable.


3
Potrebbe essere perché Nullable è stato introdotto solo in .NET 2.0 Framework, quindi prima non era disponibile?
jcolebrand,

3
Grazie Dan Burton per aver sottolineato che qualcuno PU set in seguito impostare il valore inizializzato su null sui tipi di riferimento. Pensare a ciò mi dice che il mio intento originale nella domanda non porta a nulla.
Marcel,

4

Forse se dovessi usare l' ??operatore quando assegni la variabile stringa, potrebbe esserti utile.

string str = SomeMethodThatReturnsaString() ?? "";
// if SomeMethodThatReturnsaString() returns a null value, "" is assigned to str.

2

Una stringa è un oggetto immutabile, il che significa che quando viene assegnato un valore, il vecchio valore non viene cancellato dalla memoria, ma rimane nella vecchia posizione e il nuovo valore viene inserito in una nuova posizione. Quindi, se il valore predefinito di String aera String.Empty, sprecherebbe il String.Emptyblocco in memoria quando gli viene dato il suo primo valore.

Anche se sembra minuscolo, potrebbe trasformarsi in un problema durante l'inizializzazione di una vasta gamma di stringhe con valori predefiniti di String.Empty. Ovviamente, potresti sempre usare la StringBuilderclasse mutabile se questo sarebbe stato un problema.


Grazie per aver menzionato la cosa della "prima inizializzazione".
Marcel,

3
Come sarebbe un problema quando si inizializza un array di grandi dimensioni? Poiché, come hai detto, le stringhe sono immutabili, tutti gli elementi dell'array sarebbero semplicemente puntatori allo stesso String.Empty. Mi sbaglio?
Dan Burton,

2
Il valore predefinito per qualsiasi tipo avrà tutti i bit impostati su zero. L'unico modo per il valore predefinito di stringessere una stringa vuota è consentire all-bit-zero come rappresentazione di una stringa vuota. Ci sono molti modi in cui ciò può essere realizzato, ma non credo che implicino l'inizializzazione di riferimenti String.Empty.
supercat

Anche altre risposte hanno discusso questo punto. Penso che la gente abbia concluso che non avrebbe senso trattare la classe String come un caso speciale e fornire qualcosa di diverso da all-bit-zero come un'inizializzazione, anche se fosse qualcosa di simile String.Emptyo "".
DJV

@DanV: la modifica del comportamento di inizializzazione delle stringposizioni di archiviazione avrebbe richiesto anche la modifica del comportamento di inizializzazione di tutte le strutture o classi con campi di tipo string. Ciò rappresenterebbe un cambiamento abbastanza grande nella progettazione di .net, che attualmente prevede di inizializzare zero qualsiasi tipo senza nemmeno dover pensare a cosa sia, salvo solo per le sue dimensioni totali.
supercat

2

Poiché la stringa è un tipo di riferimento e il valore predefinito per il tipo di riferimento è null.


0

Forse la stringparola chiave ti ha confuso, poiché assomiglia esattamente a qualsiasi altra dichiarazione del tipo di valore , ma in realtà è un alias di System.Stringcome spiegato in questa domanda .
Anche il colore blu scuro in Visual Studio e la prima lettera minuscola possono indurre in errore a pensare che sia un struct.


3
Lo stesso non vale per la objectparola chiave? Anche se è vero, è molto meno usato di string.

2
Come intè un alias per System.Int32. Qual è il tuo punto? :)
Thorarin,

@Thorari @delnan: Sono entrambi gli alias, ma System.Int32è un Structavendo quindi un valore di default, mentre System.Stringè un Classavere un puntatore con valore predefinito null. Sono visivamente presentati nello stesso carattere / colore. Senza conoscenza, si può pensare che agiscano allo stesso modo (= avendo un valore predefinito). La mia risposta è stata scritta con un'idea di psicologia cognitiva en.wikipedia.org/wiki/Cognitive_psychology dietro di essa :-)
Alessandro Da Rugna

Sono abbastanza sicuro che Anders Hejlsberg l'abbia detto in un'intervista sul canale 9. Conosco la differenza tra heap e stack ma l'idea con C # è che il programmatore occasionale non ne ha bisogno.
Thomas Koelle,

0

I tipi nullable non sono arrivati ​​fino alla 2.0.

Se i tipi nullable fossero stati creati all'inizio della lingua, allora stringa sarebbe stata non nullable e stringa? sarebbe stato nullable. Ma non potevano fare questo du per compatibilità con le versioni precedenti.

Molte persone parlano di ref-type o non di ref, ma la stringa è una classe fuori dal comune e sarebbero state trovate soluzioni per renderlo possibile.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.