Szukaj…


Uwagi

W ciągach .NET System.String to ciąg znaków System.Char , każdy znak jest jednostką kodowaną w UTF-16. To rozróżnienie jest ważne, ponieważ definicja znaków w mowie i definicja znaków w .NET (i wielu innych językach) są różne.

Jeden znak , który powinien być poprawnie nazwany grafemem , jest wyświetlany jako glif i jest definiowany przez jeden lub więcej punktów kodowych Unicode. Każdy punkt kodowy jest następnie kodowany w sekwencji jednostek kodowych . Teraz powinno być jasne, dlaczego pojedynczy System.Char nie zawsze reprezentuje grafhem, zobaczmy w prawdziwym świecie, jak się różnią:

  • Jeden wykres, ze względu na łączenie znaków , może skutkować dwoma lub więcej punktami kodowymi: à składa się z dwóch punktów kodowych: U + 0061 LATIN SMALL LETTER A i U + 0300 COMBINING GRAVE ACCENT . Jest to najczęstszy błąd, ponieważ "à".Length == 2 podczas gdy można się spodziewać 1 .
  • Istnieją zduplikowane znaki, na przykład à może być pojedynczym kodem U + 00E0 LATIN MAŁY LITERA A Z GRAWEJ lub dwoma kodami jak wyjaśniono powyżej. Oczywiście muszą porównać to samo: "\u00e0" == "\u0061\u0300" (nawet jeśli "\u00e0".Length != "\u0061\u0300".Length ). Jest to możliwe dzięki normalizacji łańcucha wykonanej metodą String.Normalize() .
  • Sekwencja Unicode może zawierać sekwencję złożoną lub dekomponowaną, na przykład znak U + D55C HAN CHARACTER może być pojedynczym punktem kodowym (zakodowanym jako pojedyncza jednostka kodowa w UTF-16) lub rozłożoną sekwencją jego sylab , i . Należy je porównać równe.
  • Jeden punkt kodowy może być zakodowany w więcej niż jednej jednostce kodowej: znak 𠂊 U + 2008A HAN CHARACTER jest zakodowany jako dwa System.Char ( "\ud840\udc8a" ), nawet jeśli jest to tylko jeden punkt kodowy: UTF-16 kodowanie nie ma ustalonego rozmiaru! Jest to źródło niezliczonych błędów (również poważnych błędów bezpieczeństwa), jeśli na przykład Twoja aplikacja stosuje maksymalną długość i ślepo obcina ciąg znaków, możesz utworzyć nieprawidłowy ciąg.
  • Niektóre języki mają digrafy i trygrafy, na przykład w języku czeskim ch jest samodzielną literą (po hi przed i potem przy zamawianiu listy ciągów będziesz mieć fyzikę przed chemie .

Jest dużo więcej problemów z obsługą tekstu, patrz na przykład Jak wykonać porównanie znaków znających Unicode według znaków? po szersze wprowadzenie i więcej linków do powiązanych argumentów.

Ogólnie mówiąc, w przypadku tekstu międzynarodowego możesz użyć tej prostej funkcji do wyliczenia elementów tekstowych w ciągu (unikając łamania surogatów i kodowania 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;
    }
}

Policz różne znaki

Jeśli musisz policzyć różne znaki, to z powodów wyjaśnionych w sekcji Uwagi nie możesz po prostu użyć właściwości Length ponieważ jest to długość tablicy System.Char które nie są znakami, ale jednostkami kodu (nie kodami Unicode ani grafemów). Jeśli na przykład po prostu napiszesz text.Distinct().Count() otrzymasz nieprawidłowe wyniki, popraw kod:

int distinctCharactersCount = text.EnumerateCharacters().Count();

Kolejnym krokiem jest policzenie wystąpień każdej postaci , jeśli wydajność nie jest problemem, możesz po prostu zrobić to w ten sposób (w tym przykładzie niezależnie od przypadku):

var frequencies = text.EnumerateCharacters()
    .GroupBy(x => x, StringComparer.CurrentCultureIgnoreCase)
    .Select(x => new { Character = x.Key, Count = x.Count() };

Policz znaki

Jeśli musisz policzyć znaki, to z powodów wyjaśnionych w sekcji Uwagi nie możesz po prostu użyć właściwości Length, ponieważ jest to długość tablicy System.Char które nie są znakami, ale jednostkami kodu (nie kodami Unicode ani grafemy). Poprawny kod to:

int length = text.EnumerateCharacters().Count();

Mała optymalizacja może przepisać metodę rozszerzenia EnumerateCharacters() specjalnie w tym celu:

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;
    }
}

Policz wystąpienia postaci

Z powodów wyjaśnionych w sekcji Uwagi nie możesz tego po prostu zrobić (chyba że chcesz policzyć wystąpienia określonej jednostki kodu):

int count = text.Count(x => x == ch);

Potrzebujesz bardziej złożonej funkcji:

public static int CountOccurrencesOf(this string text, string character)
{
    return text.EnumerateCharacters()
        .Count(x => String.Equals(x, character, StringComparer.CurrentCulture));
}

Zauważ, że porównanie ciągów (w przeciwieństwie do porównania znaków, które jest niezmienne w kulturze) zawsze musi być wykonywane zgodnie z regułami dla konkretnej kultury.

Podziel ciąg na bloki o stałej długości

Nie możemy podzielić łańcucha na dowolne punkty (ponieważ znak System.Char może nie być prawidłowy sam, ponieważ jest to łączący znak lub część surogatu), a następnie kod musi to wziąć pod uwagę (zwróć uwagę, że przez długość rozumiem liczbę grafemów, a nie liczbę liczba jednostek kodowych ):

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;
    }
}

Konwertuj ciąg znaków na / z innego kodowania

Ciągi .NET zawierają System.Char (jednostki kodu UTF-16). Jeśli chcesz zapisać (lub zarządzać) tekst za pomocą innego kodowania, musisz pracować z tablicą System.Byte .

Konwersje są wykonywane przez klasy pochodzące z System.Text.Encoder i System.Text.Decoder które razem mogą konwertować na / z innego kodowania (z bajtu tablicy kodowanej bajtem X na byte[] na kodowany przez UTF-16 System.String i vice -versa).

Ponieważ koder / dekoder zwykle działa bardzo blisko siebie, są one zgrupowane razem w klasie pochodzącej z System.Text.Encoding , klasy pochodne oferują konwersje do / z popularnych kodowań (UTF-8, UTF-16 i tak dalej).

Przykłady:

Konwertuj ciąg na UTF-8

byte[] data = Encoding.UTF8.GetBytes("This is my text");

Konwertuj dane UTF-8 na ciąg

var text = Encoding.UTF8.GetString(data);

Zmień kodowanie istniejącego pliku tekstowego

Ten kod odczytuje zawartość pliku tekstowego zakodowanego w UTF-8 i zapisuje go z powrotem zakodowany jako UTF-16. Pamiętaj, że ten kod nie jest optymalny, jeśli plik jest duży, ponieważ odczyta całą zawartość do pamięci:

var content = File.ReadAllText(path, Encoding.UTF8);
File.WriteAllText(content, Encoding.UTF16);

Metoda wirtualna Object.ToString ()

Wszystko w .NET jest obiektem, dlatego każdy typ ma metodę ToString() zdefiniowaną w klasie Object którą można zastąpić. Domyślna implementacja tej metody po prostu zwraca nazwę typu:

public class Foo
{
}

var foo = new Foo();
Console.WriteLine(foo); // outputs Foo

Funkcja ToString() jest domyślnie wywoływana podczas łączenia wartości za pomocą łańcucha:

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

Wynik tej metody jest również szeroko wykorzystywany przez narzędzia do debugowania. Jeśli z jakiegoś powodu nie chcesz zastąpić tej metody, ale chcesz dostosować sposób, w jaki debugger pokazuje wartość twojego typu, użyj atrybutu DebuggerDisplay ( 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;}
    // ...
}

Niezmienność ciągów

Ciągi są niezmienne. Po prostu nie możesz zmienić istniejącego ciągu. Każda operacja na łańcuchu powoduje utworzenie nowej instancji ciągu o nowej wartości. Oznacza to, że jeśli trzeba zastąpić pojedynczy znak bardzo długim łańcuchem, pamięć zostanie przydzielona na nową wartość.

string veryLongString = ...
// memory is allocated
string newString = veryLongString.Remove(0,1); // removes first character of the string.

Jeśli chcesz wykonać wiele operacji z wartością ciągu, użyj klasy StringBuilder która została zaprojektowana do efektywnej manipulacji ciągami:

var sb = new StringBuilder(someInitialString);
foreach(var str in manyManyStrings)
{
    sb.Append(str);
} 
var finalString = sb.ToString();

Porównywanie ciągów

Mimo że String znaków jest typem odniesienia == operator porównuje wartości ciągu zamiast odniesień.

Jak być może wiesz, string to po prostu tablica znaków. Ale jeśli uważasz, że sprawdzanie i porównywanie ciągów znaków jest dokonywane znak po znaku, jesteś w błędzie. Ta operacja jest specyficzna dla kultury (patrz Uwagi poniżej): niektóre sekwencje znaków mogą być traktowane jako równe w zależności od kultury .

Pomyśl dwa razy przed zwarciem sprawdzania równości porównując Length właściwości dwóch ciągów!

Użyj przeciążeń metody String.Equals , które akceptują dodatkową wartość wyliczenia StringComparison , jeśli chcesz zmienić domyślne zachowanie.



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow