2016-01-12 9 views
21

Ho notato questo strano problema. Dai un'occhiata a questo Vietnamita (secondo Google Translate) stringa:String.Starts. Non funziona con le lingue asiatiche?

string line = "Mìng-dĕ̤ng-ngṳ̄"; 
string sub = "Mìng-dĕ̤ng-ngṳ"; 
line.Length 
15 
sub.Length 
14 
line.StartsWith(sub) 
false 

che mi sembra come un torto risultato . Quindi, ho implementato la mia funzione StartWith personalizzata, che confronta la stringa char-by-char.

public bool CustomStartWith(string parent, string child) 
{ 
    for (int i = 0; i < child.Length; i++) 
    { 
     if (parent[i] != child[i]) 
      return false; 
    } 
    return true; 
} 

E come immagino, i risultati dell'esecuzione di questa funzione

CustomStartWith("Mìng-dĕ̤ng-ngṳ̄", "Mìng-dĕ̤ng-ngṳ") 
true 

quello che sta succedendo qui ?! Come è possibile?

+3

Usa una cultura invarianti. Vedi, ad esempio, http://stackoverflow.com/q/492799/1364007 –

+6

Non conosco il Vietnamita. C'è una linea sopra l'ultima 'u'. Non lo rende una lettera diversa? * Modifica: * Mi sono perso che stai stampando le lunghezze, il che sembra mostrare che la linea è considerata * un altro * personaggio ... interessante. –

+4

Mi sembra che 'StartsWith()' DOVREBBE restituire false, perché (come fa notare Jonathon), 'line' in realtà non inizia con' sub'. –

risposta

37

Il risultato restituito da StartsWith è corretto. Per impostazione predefinita, la maggior parte dei metodi di confronto delle stringhe esegue confronti sensibili alla cultura utilizzando la cultura corrente, non le sequenze di byte semplici. Sebbene il tuo line inizi con una sequenza di byte identica a sub, la sottostringa che rappresenta non è equivalente nella maggior parte (o tutte) le culture.

Se davvero si vuole un confronto che tratta stringhe come sequenze di byte semplici, utilizzare l'overload:

line.StartsWith(sub, StringComparison.Ordinal);      // true 

Se si desidera che il confronto sia case-insensitive:

line.StartsWith(sub, StringComparison.OrdinalIgnoreCase);    // true 

Ecco un altro Esempio familiare:

var line1 = "café"; // 63 61 66 E9  – precomposed character 'é' (U+00E9) 
var line2 = "café"; // 63 61 66 65 301 – base letter e (U+0065) and 
         //     combining acute accent (U+0301) 
var sub = "cafe"; // 63 61 66 65 
Console.WriteLine(line1.StartsWith(sub));        // false 
Console.WriteLine(line2.StartsWith(sub));        // false 
Console.WriteLine(line1.StartsWith(sub, StringComparison.Ordinal)); // false 
Console.WriteLine(line2.StartsWith(sub, StringComparison.Ordinal)); // true 

Negli esempi precedenti, line2 inizia con la stessa sequenza di byte di sub, seguita da un accento acuto di combinazione (U + 0301) da applicare allo e finale. line1 utilizza precomposed character per é (U + 00E9), quindi la sequenza di byte non corrisponde a quella di sub.

Nella semantica del mondo reale, in genere non si considera cafe una sottostringa di café; il e e sono trattati come caratteri distinti. Che sia rappresentato come una coppia di caratteri che iniziano con e è un dettaglio di implementazione interna dello schema di codifica (Unicode) che non dovrebbe influire sui risultati. Ciò è dimostrato dal precedente esempio in contrasto café e café; non ci si aspetterebbero risultati diversi se non si intende specificamente un confronto ordinale (byte per byte).

Adattando questa spiegazione tuo esempio:

string line = "Mìng-dĕ̤ng-ngṳ̄"; // 4D EC 6E 67 2D 64 115 324 6E 67 2D 6E 67 1E73 304 
string sub = "Mìng-dĕ̤ng-ngṳ"; // 4D EC 6E 67 2D 64 115 324 6E 67 2D 6E 67 1E73 

Ogni carattere NET rappresenta un'unità di codice UTF-16, i cui valori sono riportati nelle osservazioni sopra. Le prime 14 unità di codice sono identiche, motivo per cui il tuo confronto char-by-char viene valutato come vero (proprio come StringComparison.Ordinal). Tuttavia, la 15a unità di codice in line è il macron combinante, ◌̄ (U+0304), che combina con il precedente (U+1E73) per fornire ṳ̄.

+5

Ma che cos'è un confronto predefinito per StartsWith? Inoltre, perché usi IgnoreCase ma non solo Ordinal? L'autore usa il confronto ordinale senza ignorare il caso. –

+3

@VadimMartynov: Ho aggiornato l'esempio per usare 'Ordinal'. Spiegazione espandibile – Douglas

+1

Vale anche la pena notare che 'line1.StartsWith (line2)' è vero (e viceversa), così come 'line1.Equals (line2, StringComparison.InvariantCulture)' [e CurrentCulture per le impostazioni più ragionevoli ... l'argomento singolo Equals sembra utilizzare Ordinal] – Random832

9

Questo non è un bug.Lo String.StartsWith è in effetti molto più intelligente di un semplice controllo "carattere per carattere" delle tue due stringhe. Tiene conto della tua cultura attuale (impostazioni della lingua, ecc.) E tiene conto delle contrazioni e dei caratteri speciali. (Non interessa che siano necessari due caratteri per finire con ṳ̄. Lo confronta come uno).

Quindi questo significa che se non si desidera prendere tutte quelle impostazioni specifiche della cultura e si desidera semplicemente controllarlo utilizzando il confronto ordinale, è necessario indicare al comparatore che.

questo è il modo corretto per farlo (non ignorando il caso, come Douglas fatto!):

line.StartsWith(sub, StringComparison.Ordinal); 
+1

Ma perché non dovresti prendere in considerazione queste impostazioni specifiche della cultura? Non parlo vietnamita, queste due stringhe dovrebbero essere considerate prefissate-uguali o no? – Thilo

+0

Vale a OP. Neanch'io conosco Vietnames, ma posso capire che a volte vuoi confrontare un modo e la volta successiva il contrario. Ecco perché puoi influenzare il confronto usando "Ordinal". –

+0

Capisco. Tutto quello che sto dicendo è che l'OP dovrebbe considerare attentamente se questa è davvero la strada da percorrere. – Thilo

Problemi correlati