Kapitel 20 Indizierer
Gelegentlich ist es hilfreich, ein Objekt so indizieren
zu können, als handele es sich um ein Objekt. Dies kann erreicht
werden, indem ein Indizierer für das Objekt geschrieben wird. Diesen
kann man sich wie ein »schlaues« Array vorstellen. So wie
eine Eigenschaft wie ein Feld aussieht, jedoch über Zugriffsroutinen
zur Durchführung von get- und set-Operationen
verfügt, sieht ein Indizierer aus wie ein Array, verfügt jedoch
über Zugriffsroutinen zur Durchführung von Arrayindizierungsoperationen.
20.1 Indizierung mit einem integer-Index
 
Eine Klasse, die eine Datenbankzeile enthält, kann einen Indizierer für den
Zugriff auf die Spalten in der Zeile implementieren:
using System;
using System.Collections;
class DataValue
{
public DataValue(string name, object data)
{
this.name = name;
this.data = data;
}
public string Name
{
get
{
return(name);
}
set
{
name = value;
}
}
public object Data
{
get
{
return(data);
}
set
{
data = value;
}
}
string name;
object data;
}
class DataRow
{
public DataRow()
{
row = new ArrayList();
}
public void Load()
{
/* Hier Code laden */
row.Add(new DataValue("Id", 5551212));
row.Add(new DataValue("Name", "Fred"));
row.Add(new DataValue("Salary", 2355.23m));
}
public object this[int column]
{
get
{
return(row[column – 1]);
}
set
{
row[column – 1] = value;
}
}
ArrayList row;
}
class Test
{
public static void Main()
{
DataRow row = new DataRow();
row.Load();
DataValue val = (DataValue) row[0];
Console.WriteLine("Column 0: {0}", val.Data);
val.Data = 12; // ID setzen
}
}
Die DataRow-Klasse weist Funktionen
zum Laden einer Datenzeile und zum Speichern der Daten sowie eine Indizierungsfunktion
für den Datenzugriff auf. In einer richtigen Klasse würden
die Daten einer Datenbank über die Load()-Funktion
geladen.
Die Indizierungsfunktion ist so wie eine Eigenschaft
geschrieben, abgesehen davon, dass ein Indizierungsparameter verwendet
wird. Der Indizierer wird mit Hilfe des Namens this deklariert,
da kein Name für den Indizierer vorhanden ist.
Eine Klasse kann mehrere Indizierer aufweisen. Für
die DataRow-Klasse kann es von Nutzen sein, den Namen der
Spalte für die Indizierung einsetzen zu können:
using System;
using System.Collections;
class DataValue
{
public DataValue(string name, object data)
{
this.name = name;
this.data = data;
}
public string Name
{
get
{
return(name);
}
set
{
name = value;
}
}
public object Data
{
get
{
return(data);
}
set
{
data = value;
}
}
string name;
object data;
}
class DataRow
{
public DataRow()
{
row = new ArrayList();
}
public void Load()
{
/* Hier Code laden */
row.Add(new DataValue("Id", 5551212));
row.Add(new DataValue("Name", "Fred"));
row.Add(new DataValue("Salary", 2355.23m));
}
public object this[int column]
{
get
{
return(row[column – 1]);
}
set
{
row[column – 1] = value;
}
}
int FindColumn(string name)
{
for (int index = 0; index < row.Count; index++)
{
DataValue dataValue = (DataValue) row[index];
if (dataValue.Name == name)
return(index);
}
return(-1);
}
public object this[string name]
{
get
{
return this[FindColumn(name)];
}
set
{
this[FindColumn(name)] = value;
}
}
ArrayList row;
}
class Test
{
public static void Main()
{
DataRow row = new DataRow();
row.Load();
DataValue val = (DataValue) row["Id"];
Console.WriteLine("Id: {0}", val.Data);
Console.WriteLine("Salary: {0}",
((DataValue) row["Salary"]).Data);
((DataValue)row["Name"]).Data = "Barney"; // Name setzen
Console.WriteLine("Name: {0}", ((DataValue) row["Name"]).Data);
}
}
Der Zeichenfolgenindizierer verwendet die Funktion
FindColum() zum Auffinden des Namensindex, anschließend wird der Indizierer int
zur Durchführung der geeigneten Operation eingesetzt.
Indizierer können mehr als einen Parameter
aufweisen, um ein mehrdimensionales virtuelles Array simulieren zu können.
20.2 Indizierer und foreach
 
Wenn ein Objekt wie ein Array behandelt werden kann,
ist es oft hilfreich, dieses mit der foreach-Anweisung
zu durchlaufen. Zur Verwendung von foreach und ähnlicher
Konstruktionen in anderen .NET-Sprachen muss das Objekt die Schnittstelle
IEnumerable implementieren. Diese Schnittstelle verfügt
über ein einziges Mitglied namens GetEnumerator(),
mit dem ein Verweis auf eine IEnumerable-Schnittstelle
zurückgegeben wird, die wiederum Mitgliedsfunktionen zur Durchführung
einer Auflistung besitzt.
Die Schnittstelle IEnumerable kann
direkt durch die Containerklasse oder über eine private Klasse
implementiert werden. Die private Implementierung ist hierbei vorzuziehen,
da so die Auflistungsklasse vereinfacht wird.
Nachfolgend wird das vorstehende Beispiel um die
Verwendung von foreach erweitert:
using System;
using System.Collections;
class DataValue
{
public DataValue(string name, object data)
{
this.name = name;
this.data = data;
}
public string Name
{
get
{
return(name);
}
set
{
name = value;
}
}
public object Data
{
get
{
return(data);
}
set
{
data = value;
}
}
string name;
object data;
}
class DataRow: IEnumerable
{
class DataRowEnumerator: IEnumerator
{
public DataRowEnumerator(DataRow dataRow)
{
this.dataRow = dataRow;
index = -1;
}
public bool MoveNext()
{
index++;
if (index >= dataRow.row.Count)
return(false);
else
return(true);
}
public void Reset()
{
index = -1;
}
public object Current
{
get
{
return(dataRow.row[index]);
}
}
DataRow dataRow;
int index;
}
public DataRow()
{
row = new ArrayList();
}
public void Load()
{
/* Hier Code laden */
row.Add(new DataValue("Id", 5551212));
row.Add(new DataValue("Name", "Fred"));
row.Add(new DataValue("Salary", 2355.23m));
}
public object this[int column]
{
get
{
return(row[column – 1]);
}
set
{
row[column – 1] = value;
}
}
int FindColumn(string name)
{
for (int index = 0; index < row.Count; index++)
{
DataValue dataValue = (DataValue) row[index];
if (dataValue.Name == name)
return(index);
}
return(-1);
}
public object this[string name]
{
get
{
return this[FindColumn(name)];
}
set
{
this[FindColumn(name)] = value;
}
}
public IEnumerator GetEnumerator()
{
return((IEnumerator) new DataRowEnumerator(this));
}
ArrayList row;
}
class Test
{
public static void Main()
{
DataRow row = new DataRow();
row.Load();
foreach (DataValue dataValue in row)
{
Console.WriteLine("{0}: {1}",
dataValue.Name, dataValue.Data);
}
}
}
Die foreach-Schleife in Main() wird vom Compiler folgendermaßen
umgeschrieben:
IEnumerator enumerator = row.GetEnumerator();
while (enumerator.GetNext())
{
DataValue dataValue =
(DataValue) enumerator.Current;
Console.WriteLine("{0}: {1}",
dataValue.Name, dataValue.Data)
}

20.3 Entwurfsrichtlinien
 
Indizierer sollten nur in Situationen eingesetzt
werden, in denen die Abstraktion Sinn macht. Dies hängt normalerweise
davon ab, ob das Objekt als Container für ein anderes Objekt fungiert.
Indizierer sollten sowohl über eine Abruf-
als auch eine Festlegungsroutine verfügen, genau wie Arrays Objekte lesen und
in diesen schreiben können. Wenn der Indizierer lediglich über
eine Festlegungsroutine verfügt, sollten Sie diesen eventuell durch
eine Methode ersetzen.
|