.NET Framework
stringhe
Ricerca…
Osservazioni
Nelle stringhe .NET System.String
sono sequenze di caratteri System.Char
, ogni carattere è un'unità di codice codificata UTF-16. Questa distinzione è importante perché la definizione del linguaggio parlata di carattere e la definizione di carattere .NET (e molte altre lingue) sono diverse.
Un carattere , che dovrebbe essere correttamente chiamato grafema , viene visualizzato come un glifo ed è definito da uno o più punti di codice Unicode. Ogni punto di codice è quindi codificato in una sequenza di unità di codice . Ora dovrebbe essere chiaro il motivo per cui un singolo System.Char
non rappresenta sempre un grapheme, vediamo nel mondo reale come sono diversi:
- Un grafema, a causa della combinazione di caratteri , può risultare in due o più punti di codice: à è composto da due punti di codice: U + 0061 LATIN LETTER A e U + 0300 LIVIN COMBINING GRAVE ACCENT . Questo è l'errore più comune perché
"à".Length == 2
mentre ci si può aspettare1
. - Ci sono caratteri duplicati, ad esempio à può essere un singolo punto di codice U + 00E0 LATIN SMALL LETTER A WITH GRAVE o due code-point come spiegato sopra. Ovviamente devono confrontare lo stesso:
"\u00e0" == "\u0061\u0300"
(anche se"\u00e0".Length != "\u0061\u0300".Length
). Ciò è possibile a causa della normalizzazione delle stringhe eseguita dal metodoString.Normalize()
. - Una sequenza Unicode può contenere una sequenza composta o scomposta, per esempio il carattere 한 U + D55C HAN CHARACTER può essere un singolo punto di codice (codificato come una singola unità di codice in UTF-16) o una sequenza decomposta delle sue sillabe ᄒ , ᅡ e ᆫ . Devono essere paragonati allo stesso modo.
- Un punto di codice può essere codificato su più di una unità di codice: carattere 𠂊 U + 2008A HAN CHARACTER è codificato come due
System.Char
("\ud840\udc8a"
) anche se è solo un punto di codice: UTF-16 la codifica non è una dimensione fissa! Questa è una fonte di innumerevoli bachi (anche gravi bug di sicurezza), se per esempio la tua applicazione applica una lunghezza massima e ciecamente tronca una stringa in quel momento, puoi creare una stringa non valida. - Alcune lingue hanno digraph e trigrammi, per esempio in ch ceco è una lettera standalone (dopo le ore e prima che io poi quando si ordina una lista di stringhe si dovrà fyzika prima Chemie.
Ci sono molti più problemi sulla gestione del testo, vedi per esempio Come posso eseguire un confronto con caratteri Unicode per confronto di caratteri? per un'introduzione più ampia e più collegamenti a argomenti correlati.
In generale, quando si tratta di testo internazionale , è possibile utilizzare questa semplice funzione per enumerare gli elementi di testo in una stringa (evitando di interrompere i surrogati e la codifica Unicode):
public static class StringExtensions
{
public static IEnumerable<string> EnumerateCharacters(this string s)
{
if (s == null)
return Enumerable.Empty<string>();
var enumerator = StringInfo.GetTextElementEnumerator(s.Normalize());
while (enumerator.MoveNext())
yield return (string)enumerator.Value;
}
}
Conta personaggi distinti
Se hai bisogno di contare caratteri distinti, per le ragioni spiegate nella sezione Commenti , non puoi semplicemente usare la proprietà Length
perché è la lunghezza dell'array di System.Char
che non sono caratteri ma code-unità (non code point Unicode) né grafemi). Se, ad esempio, scrivi semplicemente text.Distinct().Count()
otterrai risultati errati, codice corretto:
int distinctCharactersCount = text.EnumerateCharacters().Count();
Un passo ulteriore è contare le occorrenze di ogni carattere , se le prestazioni non sono un problema, puoi semplicemente farlo in questo modo (in questo esempio, indipendentemente dal caso):
var frequencies = text.EnumerateCharacters()
.GroupBy(x => x, StringComparer.CurrentCultureIgnoreCase)
.Select(x => new { Character = x.Key, Count = x.Count() };
Conta personaggi
Se hai bisogno di contare i caratteri , per le ragioni spiegate nella sezione Commenti , non puoi semplicemente usare la proprietà Length perché è la lunghezza dell'array di System.Char
che non sono caratteri ma code-unità (non code-points Unicode, né grafemi). Il codice corretto è quindi:
int length = text.EnumerateCharacters().Count();
Una piccola ottimizzazione può riscrivere il metodo di estensione EnumerateCharacters()
appositamente per questo scopo:
public static class StringExtensions
{
public static int CountCharacters(this string text)
{
if (String.IsNullOrEmpty(text))
return 0;
int count = 0;
var enumerator = StringInfo.GetTextElementEnumerator(text);
while (enumerator.MoveNext())
++count;
return count;
}
}
Conta le occorrenze di un personaggio
A causa delle ragioni spiegate nella sezione Commenti , non è possibile farlo semplicemente (a meno che non si vogliano contare le occorrenze di una specifica unità di codice):
int count = text.Count(x => x == ch);
Hai bisogno di una funzione più complessa:
public static int CountOccurrencesOf(this string text, string character)
{
return text.EnumerateCharacters()
.Count(x => String.Equals(x, character, StringComparer.CurrentCulture));
}
Si noti che il confronto delle stringhe (a differenza del confronto dei caratteri, che è invariante di cultura) deve sempre essere eseguito secondo le regole di una cultura specifica.
Dividere la stringa in blocchi di lunghezza fissa
Non possiamo spezzare una stringa in punti arbitrari (perché un System.Char
può non essere valido solo perché è un personaggio che combina o parte di un surrogato) quindi il codice deve tenerne conto (notare che con lunghezza intendo il numero di grafemi non il numero di unità di codice ):
public static IEnumerable<string> Split(this string value, int desiredLength)
{
var characters = StringInfo.GetTextElementEnumerator(value);
while (characters.MoveNext())
yield return String.Concat(Take(characters, desiredLength));
}
private static IEnumerable<string> Take(TextElementEnumerator enumerator, int count)
{
for (int i = 0; i < count; ++i)
{
yield return (string)enumerator.Current;
if (!enumerator.MoveNext())
yield break;
}
}
Converti una stringa in / da un'altra codifica
Le stringhe .NET contengono System.Char
(unità di codice UTF-16). Se vuoi salvare (o gestire) il testo con un'altra codifica, devi lavorare con un array di System.Byte
.
Le conversioni vengono eseguite da classi derivate da System.Text.Encoder
e System.Text.Decoder
che, insieme, possono convertire in / da un'altra codifica (da un byte X byte codificato in byte[]
a un System.String
e vice codificati in UTF-16 -versa).
Poiché il codificatore / decodificatore di solito funziona molto vicino tra loro, vengono raggruppati in una classe derivata da System.Text.Encoding
, le classi derivate offrono conversioni alle / dalle codifiche più comuni (UTF-8, UTF-16 e così via).
Esempi:
Convertire una stringa in UTF-8
byte[] data = Encoding.UTF8.GetBytes("This is my text");
Converti dati UTF-8 in una stringa
var text = Encoding.UTF8.GetString(data);
Cambia la codifica di un file di testo esistente
Questo codice leggerà il contenuto di un file di testo con codifica UTF-8 e lo salverà codificato come UTF-16. Nota che questo codice non è ottimale se il file è grande perché leggerà tutto il suo contenuto in memoria:
var content = File.ReadAllText(path, Encoding.UTF8);
File.WriteAllText(content, Encoding.UTF16);
Object.ToString () metodo virtuale
Tutto in .NET è un oggetto, quindi ogni tipo ha il metodo ToString()
definito nella classe Object
che può essere sovrascritto. L'implementazione predefinita di questo metodo restituisce semplicemente il nome del tipo:
public class Foo
{
}
var foo = new Foo();
Console.WriteLine(foo); // outputs Foo
ToString()
viene chiamato implicitamente quando si concatena il valore con una stringa:
public class Foo
{
public override string ToString()
{
return "I am Foo";
}
}
var foo = new Foo();
Console.WriteLine("I am bar and "+foo);// outputs I am bar and I am Foo
Il risultato di questo metodo è anche ampiamente utilizzato dagli strumenti di debug. Se, per qualche motivo, non si vuole sovrascrivere questo metodo, ma si desidera personalizzare il modo in cui il debugger mostra il valore del proprio tipo, utilizzare DebuggerDisplay Attribute ( MSDN ):
// [DebuggerDisplay("Person = FN {FirstName}, LN {LastName}")]
[DebuggerDisplay("Person = FN {"+nameof(Person.FirstName)+"}, LN {"+nameof(Person.LastName)+"}")]
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set;}
// ...
}
Immutabilità delle stringhe
Le stringhe sono immutabili. Non puoi semplicemente cambiare la stringa esistente. Qualsiasi operazione sulla stringa crea una nuova istanza della stringa con un nuovo valore. Significa che se è necessario sostituire un singolo carattere in una stringa molto lunga, la memoria verrà allocata per un nuovo valore.
string veryLongString = ...
// memory is allocated
string newString = veryLongString.Remove(0,1); // removes first character of the string.
Se è necessario eseguire molte operazioni con valore stringa, utilizzare la classe StringBuilder
che è progettata per una manipolazione efficiente delle stringhe:
var sb = new StringBuilder(someInitialString);
foreach(var str in manyManyStrings)
{
sb.Append(str);
}
var finalString = sb.ToString();
Ompare le corde
Nonostante String
sia un operatore di riferimento, l'operatore ==
confronta i valori stringa anziché i riferimenti.
Come forse saprai, la string
è solo una serie di personaggi. Ma se pensi che il controllo e il paragone delle stringhe siano fatti carattere per carattere, ti stai sbagliando. Questa operazione è specifica per la cultura (vedi Note sotto): alcune sequenze di caratteri possono essere considerate uguali a seconda della cultura .
Pensaci due volte prima di corteggiare il controllo di uguaglianza confrontando le proprietà di Length
di due stringhe!
Utilizzare il sovraccarico del metodo String.Equals
che accetta il valore di enumerazione StringComparison
aggiuntivo, se è necessario modificare il comportamento predefinito.