Monthly Archives: September 2014

Reverse-String – Unicode-Aware

In vielen Beispielen wird für den Einstieg in die String-Klasse von .NET das “umdrehen” eines Strings aufgeführt. Da der String “immutable” ist, geht dies ja nicht direkt und man muss i.d.R. den Umweg über ein Array gehen. Der Code dafür schaut meistens so aus (C++/CLI):

  String^ ReverseString(String^ text)
  {
    array<Char> ^ charArray = text->ToCharArray();
    Array::Reverse(charArray);
    return gcnew String(charArray);
  }

Das Beispiel ist zwar einfach, aber es ist dann doch wieder zu einfach… es beachtet nämlich nur Zeichen, die aus genau einem Char bestehen. Im Unicode gibt es aber sher viele Zeichen (Glyph), die aus mehr als einem Char bestehen. So gibt es diverse Combining-Characters.

So kann man das “ä” als \u00e4 (sog. precomposed character) schreiben oder auch als ‘a’ + \u0308 (combining character).

Verwendet man die zweite Schreibweise, so geht das umdrehen des Strings, welche rein Char-basiert ist, schief! Dreht man dann z.B. das Wort “absa?gen” (mit combining chartacter) um, so kommt plötztlich “neg?asba” heraus, da das ? plötzlich dem “g” zugesprochen wird!

Deshalb sollte man für ein Unicode-Aweare “Reverse” immer jedes gesamte Text-Element verwenden. Und siehe da, .NET bietet hierfür natürlich auch die passenden Helferklassen 😉

String^ ReverseStringUnicodeAware(String^ text)
{
  System::Globalization::TextElementEnumerator ^enumerator = System::Globalization::StringInfo::GetTextElementEnumerator(text);
  auto elements = gcnew System::Collections::Generic::List<String^>();
  while (enumerator->MoveNext())
    elements->Add(enumerator->GetTextElement());
  elements->Reverse();
  return String::Concat(elements);
}

Es gibt da auch noch im VB.NET Namespace die Methode “Microsoft::VisualBasic::Strings::StrReverse“. Ich rate aber dringend davon ab diese zu verwenden, da diese nur ein Array::Reverse macht und somit genau die falsche Methode verwendet! (dies scheint aber wohl ein Bug zu sein, da die Implementierung dies wohl richtig machen sollte)

Hier das vollständige Beispiel:

String^ ReverseString(String^ text)
{
  array<Char> ^ charArray = text->ToCharArray();
  Array::Reverse(charArray);
  return gcnew String(charArray);
}

String^ ReverseStringUnicodeAware(String^ text)
{
  System::Globalization::TextElementEnumerator ^enumerator = System::Globalization::StringInfo::GetTextElementEnumerator(text);
  auto elements = gcnew System::Collections::Generic::List<String^>();
  while (enumerator->MoveNext())
    elements->Add(enumerator->GetTextElement());
  elements->Reverse();
  return String::Concat(elements);
}

int main()
{
  // Simple example:
  String^ str = "Hallo";
  System::Windows::Forms::MessageBox::Show("'" + str + "' => '" + ReverseString(str) + "'");

  // Combining characters: (WRONG)!
  String^ str2 = gcnew String(gcnew array<Char> { 'a', 'b', 's', 'a', 0x0308, 'g', 'e', 'n' });  // absägen
  System::Windows::Forms::MessageBox::Show("'" + str2 + "' => '" + ReverseString(str2) + "'");  // WRONG!

  // Combining characters: (CORRECT)
  System::Windows::Forms::MessageBox::Show("'" + str2 + "' => '" + ReverseStringUnicodeAware(str2) + "'");  // CORRECT!
}

Siehe auch mein (alter) Artikel über Unicode: Einführung in Codepages und Unicode