CodeGym /Kurse /C# SELF /Extension Members: Indexer

Extension Members: Indexer

C# SELF
Level 18, Lektion 4
Verfügbar

1. Einführung

Stell dir vor, wir haben unsere Lieblingsklasse DogShelter, die eine Sammlung von Hunden speichert. In den vorherigen Vorlesungen konnten wir ihr schon einen Indexer hinzufügen, um einen Hund per Nummer im Shelter zu bekommen: Dog firstDog = myShelter[0];. Ziemlich cool!

Aber was, wenn unser User einen Hund nicht per Nummer, sondern zum Beispiel per Name bekommen will? Oder nach Rasse? Oder vielleicht sogar nach einer Kombination von Eigenschaften? Klar, wir könnten Methoden wie GetDogByName("Buddy") oder GetDogByBreedAndAge("Labrador", 5) hinzufügen. Das ist auch ein völlig legitimer Ansatz.

Manchmal will man aber, dass der Zugriff noch "array-mäßiger" und intuitiver ist. Damit man sowas schreiben kann wie: Dog buddy = myShelter["Buddy"]; oder Dog oldLab = myShelter["Labrador", 8];.

Wenn DogShelter unsere eigene Klasse ist, können wir einfach neue Indexer reinpacken. Aber was, wenn DogShelter aus einer externen Bibliothek kommt, die wir nicht ändern dürfen? Oder wir wollen einen sehr speziellen Zugriff hinzufügen, der die Hauptklasse nicht "zumüllt"?

Genau hier kommen Extension Indexers (Extension Indexers) ins Spiel!

2. "Eckige Klammern" von außen

Erinnerst du dich, wie wir in der letzten Vorlesung eine Extension Property DisplayName für Dog gebaut haben? Mit Indexern läuft das ganz ähnlich!

Ein Extension Indexer ist ein statischer Indexer, der in einer statischen Klasse definiert wird und dir erlaubt, den Syntax obj[index] für Objekte bereits existierender Typen zu nutzen – auch wenn diese Typen ursprünglich keinen solchen Indexer hatten oder du einen Indexer mit anderem Parametertyp hinzufügen willst.

Das ist so, als hättest du einen Kühlschrank gekauft und dann rausfindest, wie du ihn so modifizierst, dass er dir eine Cola ausgibt, wenn du an einer bestimmten Stelle draufklopfst. Der Kühlschrank bleibt derselbe, aber die Funktionalität kommt "von außen" dazu!

Syntax eines Extension Indexers


public static class MyExtensionClass
{
    extension(ObjectType exemplar)
    {    
        public static ReturnType this[IndexTyp index ]
        {
            get
            {
                // Lese-Logik, mit exemplar und index
                return ...;
            }
            set
            {
                // Mit exemplar, index und dem Schlüsselwort 'value'
                // 'value' ist der neue Wert
            }
        }
    }        
}
Syntax eines Extension Indexers (Extension Indexer)

Achte auf this TypDesErweitertenObjekts exemplar. Dieser Syntax ist genau wie bei Extension Methods und Properties. exemplar ist der Name, den wir für das Objekt, das wir erweitern, innerhalb unserer get und set Accessoren verwenden.

3. Wie deklariert man einen Extension Indexer (ohne Gehirnknoten)?

Der Syntax ist ähnlich wie bei Extension Properties, die wir letztes Mal besprochen haben, nur eben mit Index-Parametern. Hier ein minimales Beispiel:


public static class DogShelterExtensions
{
    extension(DogShelter shelter)
    {      
        public static Dog this[string name]
        {
            get
            {
                foreach (var dog in shelter)
                {
                    if (dog.Name == name)
                        return dog;
                }
                return null;
            }
        }
    }        
}
Extension Indexer nach Name für DogShelter

Bekannte Elemente:

  • this vor dem ersten Parameter – das ist Pflicht für Extension Members (das erweiterte Objekt).
  • Nach dem Klassennamen kommt die Parameterliste, die in den eckigen Klammern verwendet wird.
Das funktioniert fast wie normale Indexer, nur dass du die Originalklasse nicht anfasst!

Praxis: Wir erweitern DogShelter mit einem Indexer nach Name

Lass uns unser Lernprojekt anpassen. Stell dir vor, wir haben ein Hundeshelter, und jeder Hund ist einzigartig nach Name:

Klasse DogShelter (Bibliothek/Fremdcode)

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

public class DogShelter : IEnumerable<Dog>
{
    private List<Dog> dogs = new List<Dog>();

    public void AddDog(Dog dog) => dogs.Add(dog);

    // Alter Indexer nach Nummer
    public Dog this[int index]
    {
        get => dogs[index];
        set => dogs[index] = value;
    }

    public IEnumerator<Dog> GetEnumerator() => dogs.GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

Wir wollen: shelter["Buddy"]

Früher – nur per Methode:

// Bis C# 14:
public static Dog? FindByName(this DogShelter shelter, string name) { ... }

Jetzt – per Extension Indexer:


public static class DogShelterExtensions
{
    extension(DogShelter shelter)
    {    
        public static Dog? this[string name]
        {
            get
            {
                foreach (var dog in shelter)
                    if (dog.Name == name)
                        return dog;
                return null; 
            }
            set
            {
                for (int i = 0; i < shelter.Count; i++)
                {
                    if (shelter[i].Name == name)
                    {
                        shelter[i] = value!;
                        return;
                    }
                }
                throw new ArgumentException("Hund nicht gefunden");
            }
        }
    }        
}

Jetzt sieht unser Hauptcode viel schicker aus:

var shelter = new DogShelter();
shelter.AddDog(new Dog { Name = "Buddy", Age = 3 });
shelter.AddDog(new Dog { Name = "Rex", Age = 5 });

// Wir nutzen den extension-Indexer!
Dog busja = shelter["Buddy"]!;
Console.WriteLine(busja.Age);

shelter["Buddy"] = new Dog { Name = "Buddy", Age = 4 };

Visualisierung: Was passiert da?

Operation Wie es früher lief Mit Extension Indexer
Suche nach Name shelter.FindByName("X") shelter["X"]
Hund nach Name ändern shelter.UpdateName("X", ..) shelter["X"] = ...

4. Feinheiten und Besonderheiten von Extension Indexers

Compiler und Sichtbarkeit

  • Ein Extension Indexer muss in einer öffentlichen statischen Klasse deklariert werden (wie auch extension methods).
  • Vergiss nicht das passende using. Wenn du das vergisst – der Compiler schweigt, aber der Code kompiliert nicht.
  • Wenn die Basisklasse schon so einen Indexer hat – kannst du ihn nicht erweitern (Signaturen müssen unterschiedlich sein).

Implementierung des set-Accessors

Du kannst nur get deklarieren (dann ist der Indexer read-only). Oder auch set (wie oben) – dann kannst du über deinen Indexer lesen und schreiben.

Wert- und Referenzübergabe

Der Extension Indexer arbeitet mit der Instanz des Objekts, auf das du das Extension draufpackst (this vor dem ersten Parameter). Wenn das Objekt ein Referenztyp ist, änderst du seinen Zustand.

Mehrere Indexer in einer Klasse

Kein Problem – du kannst mehrere extension-Indexer mit unterschiedlichen Parametern deklarieren! Zum Beispiel Suche nach Alter: shelter[5] (alt), shelter["Buddy"] (neu), shelter[age: 3] (noch einer, wenn du willst).

Beispiel: Zwei Indexer zu DogShelter hinzufügen


public static class DogShelterExtensions
{
    extension(DogShelter shelter)
    {       
        // Nach Name
        public static Dog? this[string name]
        {
            get => shelter.FirstOrDefault(d => d.Name == name);
            set
            {
                for (int i = 0; i < shelter.Count; i++)
                    if (shelter[i].Name == name)
                        shelter[i] = value!;
            }
        }

        // Nach Alter – gibt den ersten Hund mit diesem Alter zurück
        public static Dog? this[int age]
        {
            get => shelter.FirstOrDefault(d => d.Age == age);
        }
    }        
}

Jetzt kannst du schreiben:

var youngDog = shelter[1];           // Nach Alter
var tony = shelter["Toni"];          // Nach Name
shelter["Rex"] = new Dog { Name = "Rex", Age = 9 };

Echte Szenarien

  • Externe Bibliotheken: Du willst zusätzliche Indexierungsarten zu einer Fremdklasse hinzufügen, ohne die Originalquellen zu ändern. Zum Beispiel mit einer Ordersammlung arbeiten und sie nach Nummer, Datum, Status usw. finden, ohne Wrapper-Methoden zu duplizieren.
  • "Adapter-Pattern": Du machst aus einer alten Collection mit "dummem" API ein modernes, knackiges, mehr "C#-mäßiges" API, ohne die Rückwärtskompatibilität zu brechen.
  • Legacy-Code-Migration: Du fügst bestehenden Typen neue Features hinzu, ohne den existierenden Code und die Tests anzufassen.
  • Testkomfort: Du kannst temporäre Indexer für deine Tests bauen (z.B. Suche nach speziellen Testkriterien), ohne die Hauptklasse zuzumüllen.

5. Typische Fehler und Fallen bei Extension Indexers

Wenn die Basisklasse schon einen Indexer mit exakt derselben Signatur hat, wird der extension-Indexer nicht aufgerufen – der Basisindexer hat Vorrang.

Ein Extension Indexer ist immer noch ein extension member, und ohne das passende using (Namespace einbinden) ist die Erweiterung nicht sichtbar.

Ein häufiger Fehler ist, null zurückzugeben, ohne den User zu warnen. Wenn jemand versehentlich auf ein nicht-existierendes Element zugreift und der extension-Indexer gibt null zurück, kann das irgendwo im Code zu einem NullReferenceException führen. Es ist guter Stil, sich zu überlegen, was deine Implementierung tun soll: Exception werfen, ein spezielles Dummy-Objekt zurückgeben oder einfach null liefern.

Wenn du mehrere extension-Indexer hast, achte auf ihre Einzigartigkeit bezüglich Typen und Anzahl der Parameter. Zwei Indexer mit identischer Signatur sind nicht erlaubt – der Compiler meckert.

Extension-Indexer funktionieren nur mit Instanzen von Objekten, nicht mit statischen Typen.

1
Umfrage/Quiz
Einführung in Indexer, Level 18, Lektion 4
Nicht verfügbar
Einführung in Indexer
Indexer und Extension Members
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION