1. Indexer mit mehreren Parametern
Ein einfacher Indexer (zum Beispiel this[int index]) ist praktisch, wenn du Zugriff auf Elemente per Integer-Index brauchst – fast wie bei einem Array. Aber C# erlaubt viel mehr: Du kannst mehrere Parameter, Parameter mit verschiedenen Typen, unterschiedliche Zugriffsmodifikatoren für get und set verwenden und sogar mehrere Indexer in einer Klasse implementieren (wenn die Signaturen unterschiedlich sind).
Außerdem gibt es in neueren Versionen von C# zusätzliche Features, die das Arbeiten mit Indexern noch angenehmer machen, inklusive syntactic sugar für knackigen Code.
Ein Indexer muss nicht nur einen Index vom Typ int akzeptieren. Die Parameter bestimmst du – und du kannst mehrere Parameter mit unterschiedlichen Typen haben, wenn das für deine Aufgabe Sinn macht.
public class ChessBoard
{
private string[,] board = new string[8, 8];
// Indexer mit zwei Parametern!
public string this[int row, int col]
{
get { return board[row, col]; }
set { board[row, col] = value; }
}
}
Jetzt können wir den Code so schreiben:
ChessBoard chess = new ChessBoard();
chess[0, 0] = "Turm";
chess[7, 7] = "König";
string piece = chess[0, 0]; // "Turm"
Dieser Ansatz ist super für Matrizen, zweidimensionale Karten, Brettspiele, komplexe Collections.
2. Indexer mit Parametern unterschiedlichen Typs
Dein Indexer kann nicht nur Parameter vom Typ int akzeptieren, sondern auch jeden anderen passenden Typ. Hauptsache, es macht für deine Aufgabe Sinn.
using System.Collections.Generic;
public class Employee
{
public string Name { get; set; }
public int Age { get; set; }
public string Position { get; set; }
}
public class EmployeeCollection
{
private List<Employee> employees = new List<Employee>();
// Indexer nach Name des Mitarbeiters (string)
public Employee this[string name]
{
get
{
foreach (var employee in employees)
{
if (employee.Name == name)
return employee;
}
return null; // Oder du wirfst eine Exception
}
set
{
for (int i = 0; i < employees.Count; i++)
{
if (employees[i].Name == name)
{
employees[i] = value;
return;
}
}
// Wenn kein Mitarbeiter mit diesem Namen existiert – neuen hinzufügen
employees.Add(value);
}
}
// Für Kompatibilität – Indexer nach numerischem Index
public Employee this[int index]
{
get { return employees[index]; }
set { employees[index] = value; }
}
}
var company = new EmployeeCollection();
company[0] = new Employee { Name = "Ivan", Age = 30, Position = "Programmierer" };
company[1] = new Employee { Name = "Maria", Age = 25, Position = "Designer" };
Employee employee = company["Ivan"];
company["Petr"] = new Employee { Name = "Petr", Age = 28, Position = "Tester" };
Wichtig: Wenn du mehrere Indexer hast – ihre Signaturen müssen sich in Parametern und Typen unterscheiden.
3. Unterschiedliche Zugriffsmodifikatoren für get und set
Manchmal willst du nur das Lesen per Index erlauben, aber das Schreiben verbieten (oder umgekehrt). In C# kannst du unterschiedliche Zugriffsmodifikatoren für get- und set-Accessor eines Indexers setzen.
public class SecureEmployeeCollection
{
private List<Employee> employees = new List<Employee>();
public Employee this[int index]
{
get { return employees[index]; }
internal set { employees[index] = value; }
}
}
Das wird oft genutzt, um Collections vor unautorisierten Änderungen zu schützen und die Klasse kontrollierbarer zu machen.
4. Read-only und Write-only Indexer
Manchmal willst du nur das Lesen oder nur das Schreiben über den Indexer erlauben.
public class ReadOnlyEmployeeCollection
{
private List<Employee> employees = new List<Employee>();
public Employee this[int index]
{
get { return employees[index]; }
// set fehlt – ändern nicht erlaubt!
}
}
public class WriteOnlyEmployeeCollection
{
private List<Employee> employees = new List<Employee>();
public Employee this[int index]
{
set
{
employees.Insert(index, value);
}
// get fehlt – lesen nicht erlaubt!
}
}
In echten Projekten ist "write-only" fast nie zu sehen: Meistens braucht man "read-only", zum Beispiel wenn man von außen nur schauen, aber nicht per Indexer ändern darf.
5. Indexer mit Bounds-Check und Logik
In guten Beispielen ist es wichtig, nicht nur Zugriff per Index zu geben, sondern auch korrekt auf Out-of-Bounds, Suchfehler und andere Ausnahmen zu reagieren.
public class SafeEmployeeCollection
{
private List<Employee> employees = new List<Employee>();
public Employee this[int index]
{
get
{
if (index < 0 || index >= employees.Count)
throw new IndexOutOfRangeException("Mitarbeiter mit diesem Index existiert nicht!");
return employees[index];
}
set
{
if (index < 0 || index >= employees.Count)
throw new IndexOutOfRangeException("Kann keinen nicht existierenden Mitarbeiter ersetzen!");
employees[index] = value;
}
}
}
Du kannst null zurückgeben oder moderne Patterns für fehlende Werte nutzen – das hängt von der Logik deiner App ab.
6. Indexer mit ungewöhnlichen Parametern
Du kannst auf Collections treffen, wo der Indexer ungewöhnliche Typen akzeptiert: Enums (enum), eigene Strukturen, sogar mehrere Parameter mit verschiedenen Typen.
public enum Department { IT, HR, Finance, Marketing }
public class DepartmentEmployeeCollection
{
private Dictionary<Department , Employee> departmentLeads = new Dictionary<Department , Employee>();
public Employee this[Department department]
{
get { return departmentLeads.TryGetValue(department, out var employee) ? employee : null; }
set { departmentLeads[department] = value; }
}
}
var company = new DepartmentEmployeeCollection();
company[Department.IT] = new Employee { Name = "Anna", Age = 35, Position = "Leiter IT" };
Employee itLead = company[Department.IT];
Das ist praktisch, wenn du eindeutige IDs oder eine klare Zuordnung von Typ und Wert hast – bequem, verständlich und typsicher.
7. Moderne Features: Range und Index
Mit den Typen Range und Index in C# 8 haben Indexer neue Möglichkeiten für Bereiche und Indizes vom Ende bekommen:
public class SmartArray
{
private int[] numbers = Enumerable.Range(0, 100).ToArray();
public int[] this[Range range] => numbers[range];
public int this[Index index] => numbers[index];
}
// Verwendung:
var smart = new SmartArray();
int[] middle = smart[20..30]; // von Element 20 bis 29
int last = smart[^1]; // letztes Element
Wenn du deine eigene Collection baust, macht Support für Range und Index sie maximal "native" und bequem für Entwickler.
8. Properties vs. Indexer
Um besser zu verstehen, wann du Properties und wann Indexer nutzen solltest, hilft ein Vergleich ihrer Eigenschaften.
- Properties sind praktisch für den Zugriff auf individuelle Eigenschaften eines Objekts per Name: person.Name, car.Speed.
- Indexer sind für den Zugriff auf Elemente einer Collection oder Struktur per Schlüssel: employees[0], phoneBook["Ivan"].
- Eine Property hat immer einen konkreten Namen und es kann in einer Klasse nur eine Property mit diesem Namen geben.
- Ein Indexer nutzt das Schlüsselwort this und kann mehrere Varianten haben, wenn die Signaturen unterschiedlich sind.
- Properties können statisch sein, Indexer nicht, weil this immer auf eine konkrete Instanz zeigt.
9. Typische Fehler beim Arbeiten mit Indexern
Fehler Nr. 1: Keine Bounds-Prüfung.
Wenn du den Index nicht auf Out-of-Bounds prüfst, bekommst du IndexOutOfRangeException an unerwarteter Stelle.
Fehler Nr. 2: Mögliches null aus dem Indexer wird nicht behandelt.
Wenn der Indexer per String null zurückgibt, wenn das Element fehlt, und der aufrufende Code blind darauf zugreift, gibt's NullReferenceException.
Fehler Nr. 3: Doppelte Signaturen bei Indexern.
C# erlaubt keine zwei Indexer mit identischen Parameterlisten.
Fehler Nr. 4: Unklare Logik im set-Accessor.
Wenn im set "hinzufügen bei Fehlen" statt "ersetzen" gemacht wird, kann das verwirren. Solche Lösungen sollten klar und gut dokumentiert sein.
10. Praktischer Einsatz und Fazit
In echten Projekten werden Indexer oft für spezialisierte Collections, Caches, Dictionaries mit Extra-Logik, Matrizen und mehrdimensionale Datenstrukturen genutzt. Sie machen den Code lesbarer und intuitiver – statt Methoden wie GetElementByIndex(5) zu rufen, schreibst du einfach collection[5].
Denk dran: Indexer sollten logisch zur Natur deiner Klasse passen. Wenn die Klasse keine Collection oder Datenstruktur ist, braucht sie wahrscheinlich keinen Indexer. Aber wenn deine Klasse eine Menge von Elementen verwaltet, kann ein Indexer die Benutzung viel angenehmer und natürlicher machen.
GO TO FULL VERSION