CodeGym /Kurse /C# SELF /Interface IComparable<T...

Interface IComparable<T>

C# SELF
Level 30 , Lektion 0
Verfügbar

1. Einführung

Stell dir vor, du bittest einen Freund, Bücher ins Regal zu sortieren. Wenn das Bücher mit Nummern auf dem Buchrücken sind (1, 2, 3…), macht dein Freund das easy: "Okay, 1 kommt vor 2, 2 vor 3." Das ist die natürliche Reihenfolge von Zahlen (der Reihe nach). Genauso, wenn es Bücher sind, deren Titel mit "A", "B", "C" anfangen, sortiert er sie alphabetisch. Auch das ist die "natürliche" Reihenfolge für Strings (alphabetisch).

Aber was, wenn auf den Buchrücken nur die Nachnamen der Autoren stehen? Und du sagst: "Sortier mal der Reihe nach." Dein Freund fragt: "Nach welcher Reihenfolge? Nach Nachname? Nach Erscheinungsjahr? Nach Seitenanzahl?" Genau da fängt das Problem an. Für deine eigenen Objekte gibt es keine offensichtliche natürliche Reihenfolge.

Genauso ist es beim Programmieren. Wenn wir eine Liste von ganzen Zahlen List<int> sortieren wollen, weiß C# sofort, wie das geht. Für List<string> klappt das auch, da wird lexikographisch (alphabetisch) sortiert. Aber wenn wir eine List<Student> haben, wo jeder Student ein Objekt mit Name, Nachname, Alter, ID und noch mehr ist, dann ist C# lost. Es weiß nicht, nach welchem Kriterium zwei Studenten verglichen werden sollen. Nach Name? Nach ID? Nach Durchschnittsnote? Genau das ist das Problem, das wir in der Vorlesung "Sortieren von Collections" hatten, als List<T>.Sort() einen Fehler geworfen hat, wenn wir versucht haben, eine Collection von eigenen Typen zu sortieren.

Um dieses Rätsel zu lösen, müssen wir C# eine klare Ansage machen, nach welchem Prinzip unsere Objekte verglichen werden sollen. Dafür gibt es das Interface IComparable<T>.

2. Interface IComparable<T>

Also, damit unsere Objekte "wissen", wo sie im Vergleich zu anderen stehen, nutzen wir das Interface IComparable<T>. Das ist ein Vertrag. Wenn deine Klasse oder Struktur dieses Interface implementiert, sagst du dem Compiler quasi: "Hey! Meine Objekte kann man miteinander vergleichen und hier ist die Anleitung, wie das geht."

Wie läuft das ab?

Das Interface IComparable<T> definiert genau eine Methode:

public interface IComparable<in T>
{
    int CompareTo(T other);
}

Diese Methode nimmt ein Objekt vom Typ T (dem gleichen Typ wie das aktuelle Objekt) und soll zurückgeben:

  • eine negative Zahl (< 0), wenn das aktuelle Objekt "kleiner" ist als das verglichene;
  • null (0), wenn sie "gleich" sind (aus Sicht der Sortierung);
  • eine positive Zahl (> 0), wenn das aktuelle Objekt "größer" ist.

Im Grunde ist das wie bei Schiedsrichtern im Boxkampf: Wenn du überzeugender warst, gewinnst du die Runde und bekommst mehr Punkte; wenn schwächer — weniger; wenn der Kampf ausgeglichen war, gibt's Gleichstand. Nur ist es hier noch simpler: eine Zahl mit Vorzeichen.

Warum genau so?

Sortiermethoden (wie List<T>.Sort()) rufen CompareTo für die Elemente der Liste auf, um zu checken, wer zuerst kommt und wer später. Wenn deine Klasse dieses Interface implementiert — kann sie sortiert werden!

3. Praxis

Angenommen, wir haben so eine User-Klasse (User):

public class User
{
    public string Name { get; set; }
    public int Age { get; set; }
}

Probieren wir mal, eine Liste von Usern zu sortieren:

List<User> users = new List<User>
{
    new User { Name = "Sergej", Age = 31 },
    new User { Name = "Maria", Age = 22 },
    new User { Name = "Anton", Age = 27 }
};

users.Sort(); // BOOM! InvalidOperationException

Es kommt ein Fehler: "Mindestens ein Objekt muss IComparable implementieren" (Mindestens ein Objekt muss IComparable implementieren).

Fix: IComparable<User> implementieren

Wir fügen das Interface zu unserer Klasse hinzu. Lass uns erstmal nach Alter sortieren — von jung nach alt:

public class User : IComparable<User>
{
    public string Name { get; set; }
    public int Age { get; set; }

    public int CompareTo(User other)
    {
        // Null-Check: Wenn other == null, ist unser User "größer"
        if (other == null) return 1;
        return this.Age.CompareTo(other.Age); // sortieren nach Alter
    }
}

Jetzt erstellen wir eine Liste, rufen users.Sort(); auf und geben das Ergebnis in der Konsole aus:

List<User> users = new List<User>
{
    new User { Name = "Sergej", Age = 31 },
    new User { Name = "Maria", Age = 22 },
    new User { Name = "Anton", Age = 27 }
};

// Sortieren nach Alter (nutzt CompareTo)
users.Sort();

// Ausgabe der sortierten User
foreach (User user in users)
{
    Console.WriteLine($"{user.Name}, {user.Age}");
}

Die Liste wird nach Alter sortiert:

Maria, 22
Anton, 27
Sergej, 31

Visualisierung: vorher und nachher

Name Alter
Sergej 31
Maria 22
Anton 27

Nach dem Sortieren:

Name Alter
Maria 22
Anton 27
Sergej 31

4. Wichtige Details und typische Fehler

Null-Check (null)

In CompareTo ist es super wichtig zu prüfen, dass other nicht null ist. Wenn du das vergisst, kann dein Objekt ein NullReferenceException bekommen — und die sind so fies wie Bugs kurz vorm Release. Normalerweise gilt: Wenn das verglichene Objekt null ist, ist das aktuelle "größer":

public int CompareTo(User other)
{
    if (other == null) return 1;
    // ...
}

Transitivität einhalten

Wenn A < B und B < C, dann muss auch A < C sein. Wenn du das nicht beachtest, verhält sich die Sortierung unvorhersehbar (also lustig, aber falsch!).

Wenn nach mehreren Feldern sortiert werden soll

Angenommen, du willst erst nach Alter sortieren, aber wenn zwei User gleich alt sind, dann nach Name alphabetisch. Das geht so:

public int CompareTo(User other)
{
    if (other == null) return 1;

    int ageCompare = this.Age.CompareTo(other.Age);
    if (ageCompare != 0) return ageCompare;

    // Wenn das Alter gleich ist — nach Name vergleichen
    return this.Name.CompareTo(other.Name);
}

5. Sortieren: jetzt auch mit deinen eigenen Objekten

Alles, was List<int> kann — kann jetzt auch deine Klasse

Jetzt kannst du jede Methode nutzen, die Vergleiche braucht: Sort, BinarySearch, sogar das Einfügen in sortierte Collections (wie SortedSet<T>).

users.Sort();
// users ist jetzt nach Alter sortiert (und nach Name bei gleichem Alter)

Beispiel im Kontext einer Kurs-App

Angenommen, du hast schon mal eine App zur Verwaltung von Usern gebaut. Jetzt kannst du ihnen eine "natürliche" Sortierreihenfolge direkt im bestehenden Code geben. So sieht das aus:

// Unsere User-Klasse implementiert schon IComparable<User>

List<User> users = new List<User>
{
    new User { Name = "Ivan", Age = 45 },
    new User { Name = "Galina", Age = 27 },
    new User { Name = "Jurij", Age = 27 }
};

// Erst nach Alter, dann nach Name sortieren
users.Sort();
foreach (var u in users)
{
    Console.WriteLine($"{u.Name} - {u.Age}");
}

Ergebnis:

Galina - 27
Jurij - 27
Ivan - 45

Galina und Jurij sind gleich alt, also wird bei gleichem Alter nach Name sortiert.

6. Wie CompareTo funktioniert: harte Mathematik

Nochmal der Fokus auf den Rückgabewerten:

  • Negative Zahl (z.B. -1): Das aktuelle Objekt kommt vor dem verglichenen.
  • Null: Sie gelten als gleich für die Sortierung.
  • Positive Zahl (z.B. 1): Das aktuelle Objekt kommt nach dem verglichenen.

Die eingebauten Typen (z.B. Age.CompareTo(other.Age)) halten diesen Standard schon ein und geben immer -1, 0 oder 1 zurück.

Rückgabewerte-Tabelle für CompareTo

Rückgabewert Bedeutung Beispiel
< 0 Kleiner (kommt vorher)
20.CompareTo(21): -1
0 Gleich
23.CompareTo(23): 0
> 0 Größer (kommt später)
42.CompareTo(15): 1

7. Mehrfachsortierung: Felder kombinieren

Manchmal brauchst du eine komplexere Sortierung: zum Beispiel nach Nachname, Vorname und Alter. Mit dem bekannten Trick kannst du nacheinander vergleichen:

public class Student : IComparable<Student>
{
    public string LastName { get; set; }
    public string FirstName { get; set; }
    public int Grade { get; set; }

    public int CompareTo(Student other)
    {
        if (other == null) return 1;
        int lastNameCompare = this.LastName.CompareTo(other.LastName);
        if (lastNameCompare != 0) return lastNameCompare;
        int firstNameCompare = this.FirstName.CompareTo(other.FirstName);
        if (firstNameCompare != 0) return firstNameCompare;
        return this.Grade.CompareTo(other.Grade);
    }
}

8. Wann du IComparable<T> NICHT implementieren solltest

Ein Beispiel aus dem (Programmier-)Leben: Wenn ein Objekt keine "natürliche" Sortierreihenfolge hat, solltest du IComparable<T> besser nicht implementieren. Zum Beispiel, wenn du eine Point-Klasse hast, aber nicht weißt — nach X, nach Y oder nach Entfernung vom Ursprung sortieren? Dann gib die Sortierung lieber von außen mit einer Vergleichsfunktion (IComparer<T>) vor. Darüber reden wir in der nächsten Vorlesung.

2
Aufgabe
C# SELF, Level 30, Lektion 0
Gesperrt
Implementierung des Interfaces IComparable für den Vergleich nach einem Feld
Implementierung des Interfaces IComparable für den Vergleich nach einem Feld
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION