1. Mehrstufiges (hierarchisches) Sortieren
Warum ist Vergleichen nicht immer einfach?
Bei C#-Interviews werden oft Fragen zum Sortieren und Vergleichen von komplexen Objekten gestellt. Das ist nicht nur ein Trend, sondern spiegelt echte Probleme wider, mit denen jeder Entwickler konfrontiert wird. Datensätze in der Datenbank suchen, Oberflächen und Tabellen sortieren, Eindeutigkeit in Collections – all das hängt direkt davon ab, wie korrekt Objekte verglichen werden.
Zahlen zu vergleichen ist easy. Aber wenn wir User nach Nachname, dann nach Vorname sortieren wollen, und dabei noch beachten müssen, dass manche Felder leer (null) sein können oder Strings in verschiedenen Sprachen geschrieben sind... Da braucht man schon einen systematischen Ansatz.
Oft gefordert: Erst nach einem Kriterium sortieren, dann – falls gleich – nach dem nächsten, und falls immer noch gleich – nach einem dritten.
Beispiel: User mit Vorname, Nachname und Geburtsdatum
public class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime BirthDate { get; set; }
// Für die Optik – User-Info ausgeben
public override string ToString()
=> $"{LastName} {FirstName} ({BirthDate:yyyy-MM-dd})";
}
Warum braucht man Hierarchie?
Stell dir vor, wir haben eine User-Liste und wollen sie alphabetisch ausgeben: Erst nach Nachname, dann nach Vorname. Wenn Nachname und Vorname gleich sind – nach Geburtsdatum.
Vergleichslogik: "Chain of Responsibility"
Das ist wie bei Olympia-Teilnehmern: Erst nach Punkten, bei Gleichstand nach Abgabezeit, und wenn immer noch gleich – alphabetisch. Im Code geht das easy:
public class UserComparer : IComparer<User>
{
public int Compare(User x, User y)
{
// Nachnamen vergleichen
int result = string.Compare(x.LastName, y.LastName, StringComparison.OrdinalIgnoreCase);
if (result != 0) return result; // Unterschied? Fertig.
// Wenn Nachnamen gleich – Vornamen vergleichen
result = string.Compare(x.FirstName, y.FirstName, StringComparison.OrdinalIgnoreCase);
if (result != 0) return result;
// Wenn Nachname und Vorname gleich – Geburtsdatum vergleichen
return x.BirthDate.CompareTo(y.BirthDate);
}
}
So benutzt du das:
var users = new List<User>
{
new User { FirstName = "Ivan", LastName = "Ivanov", BirthDate = new DateTime(1990, 1, 1) },
new User { FirstName = "Petr", LastName = "Ivanov", BirthDate = new DateTime(1992, 5, 1) },
new User { FirstName = "Anna", LastName = "Petrova", BirthDate = new DateTime(1985, 8, 30) }
};
users.Sort(new UserComparer());
users.ForEach(Console.WriteLine);
// Petrova Anna (1985-08-30)
// Ivanov Ivan (1990-01-01)
// Ivanov Petr (1992-05-01)
2. Wie vergleicht man Strings? Kulturelle Besonderheiten
String-Vergleich: Ordinal, CurrentCulture, InvariantCulture
String ist nicht gleich String! Zum Beispiel russisches ё und е, deutsches ss und ß, Groß-/Kleinschreibung...
In .NET gibt es spezielle Regeln für String-Vergleiche, die du über StringComparison festlegst. Das beeinflusst Sortierung und Suche.
Beispiel für String-Vergleich:
// Im Deutschen ist ß fast wie ss
string a = "straße";
string b = "STRASSE";
bool eq1 = string.Equals(a, b, StringComparison.Ordinal); // false
bool eq2 = string.Equals(a, b, StringComparison.OrdinalIgnoreCase); // false
bool eq3 = string.Equals(a, b, StringComparison.CurrentCultureIgnoreCase); // true
In den ersten beiden Fällen wird byteweise verglichen – ohne Kultur oder Sprachregeln, daher sind ß und SS verschieden. Im dritten Fall wird die aktuelle Kultur (z.B. Deutsch) genutzt, und der String wird so gelesen, wie es ein Muttersprachler tun würde: ß gilt als ss, Groß-/Kleinschreibung wird ignoriert. Deshalb gibt eq3 true zurück.
Wie wählst du die richtige Vergleichsart?
- Ordinal – schnell, byteweise, gut für technische Sachen (z.B. ID-Vergleich).
- CurrentCulture / InvariantCulture – für User-Texte, beachtet Sprachregeln des OS oder der eingestellten Kultur.
Bei Sort, Compare und Co. gib immer explizit die Vergleichsart an:
string.Compare(x, y, StringComparison.CurrentCultureIgnoreCase)
Sortierbesonderheiten in verschiedenen Sprachen
Sortieren auf Russisch – ё kann nach е kommen oder als gleich gelten (je nach Kultur!). Wenn du was Ernstes baust (z.B. ein Nachschlagewerk), frag beim Kunden oder Business-Analysten nach, wie "besondere" Buchstaben sortiert werden sollen.
3. Schutz vor null: Nicht alle Objekte sind brav
Was tun, wenn ein Feld null ist?
In echten Programmen vergisst garantiert jemand, ein Feld zu füllen, und beim Vergleichen – bumm! – NullReferenceException. Deine Aufgabe: Sei vorbereitet.
Beispiel ohne null-Schutz:
public int Compare(User x, User y)
{
return x.LastName.CompareTo(y.LastName); // wenn LastName == null, gibt's nen Fehler!
}
Beispiel mit Schutz (null ist kleiner als jeder nicht-null):
public int Compare(User x, User y)
{
// Spezieller String-Comparer, der null beachtet
int byLastName = Comparer<string>.Default.Compare(x.LastName, y.LastName);
if (byLastName != 0) return byLastName;
// usw...
}
Noch kürzer:
public int Compare(User x, User y)
{
return string.Compare(x?.LastName, y?.LastName, StringComparison.OrdinalIgnoreCase);
}
"Nulls" am Anfang oder Ende?
Du kannst es so machen, dass alle User mit "leeren" Nachnamen am Anfang oder Ende der Liste stehen – je nach Aufgabe.
public int Compare(User x, User y)
{
if (x.LastName == null && y.LastName == null) return 0;
if (x.LastName == null) return 1; // null ans Ende
if (y.LastName == null) return -1; // null ans Ende
return string.Compare(x.LastName, y.LastName, StringComparison.OrdinalIgnoreCase);
}
4. Vergleich nach mehreren Kriterien
Typischer Anfängerfehler: Mathe statt "Chain of Responsibility" beim Vergleichen, z.B.:
// Mach das nicht!
public int Compare(User x, User y)
{
// Schlechtes Beispiel
return (x.Age - y.Age) + string.Compare(x.FirstName, y.FirstName, StringComparison.Ordinal);
}
Das garantiert keine korrekte Sortierung: Wenn Altersunterschied -100 ist und String-Vergleich 1 zurückgibt, ist das Ergebnis -99 – das passt nicht zur erwarteten Sortierlogik.
Richtig ist eine klare Reihenfolge:
Wenn es schon einen Unterschied gibt – gib ihn zurück, sonst schau aufs nächste Kriterium.
5. Nützliche Details
Was tun, wenn Objekte nach Hauptkriterien gleich sind?
Wenn Objekte "gleich" sind, ist es wichtig, dass der Sortieralgorithmus stabil ist: Er darf die Reihenfolge von Elementen, die laut Vergleich gleich sind, nicht ändern. Das eingebaute List<T>.Sort() garantiert keine Stabilität. Wenn das wichtig ist (z.B. bei Tabellen mit mehreren User-Sortierleveln), nutze die LINQ-Methoden OrderBy/ThenBy – die sind stabil.
Vergleich mit optionalen/nullable Feldern
In .NET gibt's oft Modelle, wo ein Feld z.B. DateTime? (Nullable<DateTime>) oder int? ist. Die Logik: null ist kleiner als nicht-null, oder umgekehrt – je nach Aufgabe. Du kannst Helfer aus der Standardbibliothek nutzen:
int result = Nullable.Compare<DateTime>(u1.BirthDate, u2.BirthDate);
Vergleich mit zusätzlichen Regeln
Manchmal soll der Vergleich das "Gewicht" eines Kriteriums beachten, z.B. VIP-Kunden immer zuerst. Lösung: "VIP-Flag" am Anfang der Sortierung.
public int Compare(User x, User y)
{
// VIP zuerst
int vipResult = y.IsVip.CompareTo(x.IsVip); // true = 1, false = 0; absteigend sortieren
if (vipResult != 0) return vipResult;
// Rest wie gewohnt
int result = string.Compare(x.LastName, y.LastName, StringComparison.OrdinalIgnoreCase);
if (result != 0) return result;
return string.Compare(x.FirstName, y.FirstName, StringComparison.OrdinalIgnoreCase);
}
6. Tipps und Best Practices
Immer auf null prüfen
Modernes C# will immer mehr "null-sicher" sein, aber alter Code ist es oft nicht. Baue immer Schutz ein oder nutze passende Methoden.
Vermeide "magische" Zahlen
Schreib nicht return x.Field - y.Field; bei Feldern, wo ein Überlauf (overflow) möglich ist. Bei long-Feldern gibt's da garantiert Fehler.
Nutze StringComparison
Verlass dich nicht auf das Standardverhalten beim String-Vergleich. Gib explizit StringComparison.OrdinalIgnoreCase oder was Passendes an.
Vergleich und Gleichheit trennen
Die Interfaces IComparable<T> und IEqualityComparer<T> sind für verschiedene Aufgaben. Zum Sortieren: Comparer, für eindeutige Objekte: Gleichheit. Die können auch mal unterschiedliche Ergebnisse liefern!
Teste "untypische" Fälle
Check, ob die Sortierung klappt, wenn Felder gleich sind, Felder leer sind, Strings unterschiedliche Groß-/Kleinschreibung oder Sprache haben.
GO TO FULL VERSION