Nell'ordine in cui il codice lo colpisce ...
==
viene ignorata. Ciò significa che anziché "abc" == "ab" + "c"
chiamando il valore predefinito ==
per i tipi di riferimento (che confronta riferimenti e non valori) chiama in string.Equals(a, b)
.
Ora, questo fa la seguente:
- Se i due sono in effetti lo stesso riferimento, tornare vero.
- Se entrambi sono nulli, restituiscono false (avremmo già restituito true se erano entrambi nulli).
- se i due sono di lunghezza diversa, restituiscono false;
- Esegui un ciclo ottimizzato attraverso una stringa, confrontandolo char-for-char con il resto (in realtà int-for-int visto come due blocchi di ints in memoria, che è una delle ottimizzazioni in questione). Se raggiunge la fine senza una mancata corrispondenza, restituisce true, altrimenti restituisce false.
In altre parole, si inizia con qualcosa di simile:
public static bool ==(string x, string y)
{
//step 1:
if(ReferenceEquals(x, y))
return true;
//step 2:
if(ReferenceEquals(x, null) || ReferenceEquals(y, null))
return false;
//step 3;
int len = x.Length;
if(len != y.Length)
return false;
//step 4:
for(int i = 0; i != len; ++i)
if(x[i] != y[i])
return false;
return true;
}
parte il fatto che il punto 4 è una versione puntatore-based con un loop srotolato che dovrebbe quindi essere idealmente più veloce. Non lo mostrerò perché voglio parlare della logica generale.
Esistono scorciatoie significative. Il primo è al punto 1. Poiché l'uguaglianza è riflessiva (l'identità implica l'uguaglianza, a == a
), allora possiamo restituire true in nanosecondi anche per una stringa di diverse dimensioni di MB, se confrontata con se stessa.
Il passaggio 2 non è una scorciatoia, perché è una condizione che deve essere testata, ma si noti che poiché avremo già restituito true per (string)null == (string)null
non abbiamo bisogno di un altro ramo. Quindi l'ordine di chiamata è orientato verso un risultato rapido.
Il passaggio 3 consente due cose. È sia scorciatoie su stringhe di lunghezza diversa (sempre false) e significa che non è possibile accidentalmente sparare oltre la fine di una delle stringhe confrontate nel passaggio 4.
Si noti che questo non è il caso per altri confronti di stringhe , poiché ad es WEISSBIER
e weißbier
sono lunghezze diverse ma la stessa parola in diverse lettere maiuscole, quindi il confronto senza distinzione tra maiuscole e minuscole non può utilizzare il passaggio 3. Tutti i confronti di uguaglianza possono eseguire i passaggi 1 e 2 in base alle regole utilizzate sempre, quindi è necessario utilizzarli solo alcuni possono fare il passaggio 3.
Quindi, mentre si sbagliano nel suggerire che si tratta di riferimenti piuttosto che di valori confrontati, è vero che i riferimenti vengono prima confrontati come una scorciatoia molto significativa. Si noti inoltre che le stringhe internate (stringhe inserite nel pool interno mediante la compilazione o da string.Intern
chiamate) attiveranno quindi questa scorciatoia spesso. Questo sarebbe il caso nel codice dell'esempio, in quanto il compilatore avrà utilizzato lo stesso riferimento in ciascun caso.
Se sai che una stringa è stata internata, puoi fare affidamento su questo (fai solo un test di uguaglianza di riferimento), ma anche se non sai per certo che puoi trarne beneficio (test di uguaglianza di riferimento sarà almeno una scorciatoia qualche volta).
Se si dispone di un gruppo di stringhe in cui si desidera testare spesso alcune di esse l'una contro l'altra, ma non si desidera estendere la loro durata in memoria tanto quanto internare, è possibile utilizzare uno XmlNameTable o LockFreeAtomizer (presto rinominato ThreadSafeAtomizer e il documento spostato in http://hackcraft.github.com/Ariadne/documentation/html/T_Ariadne_ThreadSafeAtomizer_1.htm - dovrebbe essere stato nominato per la funzione anziché i dettagli di implementazione in primo luogo).
Il primo è utilizzato internamente da XmlTextReader
e quindi da molto del resto di System.Xml
e può essere utilizzato anche da altro codice. Quest'ultimo ho scritto perché volevo un'idea simile, che fosse sicura per le chiamate simultanee, per tipi diversi e dove potevo ignorare il confronto di uguaglianza.
In entrambi i casi, se si mettono 50 stringhe diverse che sono tutte "abc" in esso, si otterrà un unico riferimento "abc" che consentirà agli altri di essere triturati. Se sai che questo è successo, puoi contare solo su ReferenceEquals
e, se non sei sicuro, trarrai comunque vantaggio dalla scorciatoia quando è il caso.
@EricJ. Ma se hanno lo stesso indirizzo di memoria ne consegue che il ** deve ** avere lo stesso contenuto (è la stessa * istanza * dopo tutto). – Yuck
@Yuck - Solo se interning fa parte della specifica e non solo un dettaglio di implementazione. Inoltre, le stringhe in App Domain separate potrebbero essere uguali e avere indirizzi diversi. – psr
@psr Giusto, ecco perché il controllo condizionale. Se il riferimento è lo stesso, allora hai finito - è tutto. Altrimenti dovresti confrontare il contenuto di ciascuna variabile per determinare l'uguaglianza logica. – Yuck