Index-basierter Zugriff auf Objekte in C# (und PHP) 👍 👎

Manchmal kann es sinnvoll (oder zumindest bequem oder intuitiv) sein, auf ein Objekt im Stile eines Feldes zugreifen zu können. Für das folgende Beispiel zu diesem Thema möchte ich kurz einen Anwendungsfall aus meiner Arbeit beschreiben.

Als ISP müssen wir bei Domains des Öfteren mit sog. "Handles" umgehen. Grob umrissen handelt es sich dabei um einen Kontakt-Datensatz für eine Domain. Darin abgelegt werden beispielsweise der Name, die Anschrift und – je nach TLD – auch noch div. andere Daten.

Im einfachsten Fall verwendet man zur Ablage dieser Informationen also ein assoziatives Feld (in C# z. B. ein generisches Dictionary) als Eigenschaft des Objekts:
"Handle"-Klasse
010203040506
public class Handle {    public Dictionary<string,string> HandleFieldValueList  {        get;        set;    }}
Den Umgang mit derartigen Datenfeldern habe ich zwar schon einmal relativ ausführlich beschrieben, dennoch möchten wir auch hier erst einmal das Objekt mit Daten füllen:
Daten hinzufügen
010203040506070809
Handle ich = new Handle() {      // Feldwerte    HandleFieldValueList = new Dictionary<string,string>() {        {"firstName", "Holger"},        {"lastName", "Stehle"}    }};
// ich.HandleFieldValueList["firstName"] enthält "Holger" etc.
Wie bereits angedeutet, können wir nun wie erwartet auf die entsprechenden Daten zugreifen. In einem solchen Fall – in dem die einzelnen Werte schließlich das Handle an sich ausmachen – wäre es doch aber auch praktisch, diesen Index-basierten Zugriff direkt auf dem Objekt durchführen zu können. Und genau dies ist mit C# ganz einfach möglich. Dazu müssen wir die Klasse wie folgt erweitern:
Indexer ergänzen
01020304050607080910111213141516171819
public class Handle {      // Indexer (-> Feldwerte)    public string this[string fieldName] {        get {            return this.HandleFieldValueList[fieldName];        }        set {            this.HandleFieldValueList[fieldName] = value;        }    }
// Feldwerte public Dictionary<string,string> HandleFieldValueList { get; set; }}
// ich["firstName"] enthält "Holger" etc.
Dies veranschaulicht die Implementierung eines sog. Indexers, welcher sich grundlegend wie eine Eigenschaft verhält. Der hauptsächlich interessante Teil string this[string fieldName] besagt, dass ein Index-basierter Zugriff auf die Instanz (→ this) mit einem Index (→ fieldName) des Typs string einen string liefert und definiert im Stile eines Akzessors, wie genau dies geschieht. Und schon können wir (wie im Code angedeutet) wie erhofft darauf zugreifen.

Indexer können auch überladen werden, so dass z. B. zusätzlich auch ein Zugriff mit numerischem Index ermöglicht werden kann. Zuletzt gilt zu beachten: Es handelt sich hierbei lediglich um eine etwas kompaktere Schreibweise für etwas, was sowieso möglich wäre. Dennoch gibt es – wie ich persönlich finde – interessante Einsatzmöglichkeiten hierfür. Der konkrete Nutzen ist also im Einzelfall abzuwägen.

Um derartiges Verhalten in PHP zu ermöglichen, muss die Klasse die Schnittstelle ArrayAccess implementieren. Ein Beispiel zur Umsetzung findet sich z. B. in der offiziellen Dokumentation.

Partielle Klassen in C# 👍 👎

C# unterstützt sogenannte partielle Klassen, welche es ermöglichen, eine Klasse auf verschiedene Dateien verteilt zu definieren. Diese werden bei der Kompilierung zu einer einzelnen Klasse zusammengeführt, so als wäre sie wie eine "normale" Klasse in einer Datei definiert worden. Insbesondere ist also auch dateiübergreifend Zugriff auf private Mitglieder (Eigenschaften, Methoden etc.) einer Klasse möglich.

Wir wollen dies wie üblich an einem (kleinen) Beispiel konkretisieren und legen dazu einfach die Eigenschaften und Methoden einer Klasse in zwei verschiedenen Dateien ab:
Klasse "Person" in Datei "Person.cs"
01020304050607080910111213141516
public partial class Person {    public Person(string firstName, string lastName) {        this.FirstName = firstName;        this.LastName = lastName;    }
// Partner liefern public Person GetPartner() { // z. B. aus Datenbank auslesen }
// Kinder liefern public List<Person> GetChildren() { // z. B. aus Datenbank auslesen }}
Klasse "Person" in Datei "Person.Properties.cs"
01020304050607080910111213141516
public partial class Person {    public string FirstName {        get;        set;    }
public string LastName { get; set; }
public int Age { get; set; }}
Zugegeben: Es ist fraglich, ob dieses Vorgehen – insbesondere in diesem trivialen Beispiel – überhaupt sinnvoll ist. Andererseits ergibt sich für den Aufrufer/Verwender der Klasse keinerlei Unterschied zur Definition in einer Datei, da letztlich eine einzige Klasse herauskommt. Insofern ist dem Entwickler (bzw. den Richtlinien des Teams) überlassen, ob und ggf. wie man damit umgehen möchte. Die Verwendung erfolgt jedenfalls ganz wie gewohnt:
Anwendung der partiell definierten Klasse
010203
Person ich = new Person("Holger", "Stehle") {    Age = 24};
Ein großer Vorteil ergibt sich jedoch bei der Verwendung von Code-Generatoren. Der generierte Code definiert dabei partielle Klassen. Diese können nun in einer eigenen Datei erweitert werden, ohne Gefahr zu laufen, bei einer neuen automatischen Codegenerierung alle Anpassungen zu verlieren. Visual Studio selbst setzt auf derartiges Vorgehen z. B. im Zusammenhang mit ADO.NET Entity Framework oder grafischen Benutzeroberflächen (vgl. "*.Designer.cs"-Dateien).

Andere Einsatzmöglichkeiten wären die übersichtliche Arbeit verschiedener Entwickler an einer umfangreicheren Klasse oder das Anbieten einer Anwendung mit verschiedenen Ausprägungen der Funktionalität einer Klasse, indem nur spezifische Dateien der partiellen Klasse kompiliert werden.

Mit der praktischen kleinen Erweiterung NestIn für Visual Studio (nicht Express) könnt ihr im Übrigen mit wenigen Mausklicks auch dafür sorgen, dass die Dateien wie aus der Entwicklungsumgebung bekannt verschachtelt dargestellt werden, was meiner Ansicht nach deutlich zur Übersicht beiträgt.

Galerie 👍 👎

Ich habe eine kleine Galerie gestartet, die unregelmäßig erweitert werden soll.

Schlagworte und Suche 👍 👎

Ab sofort findet ihr hier im Blog Schlagworte zu den einzelnen Beiträgen.

Darüber hinaus können die Beiträge nun auch bequem durchsucht werden.

Objekt- und Auflistungsinitialisierer in C# 👍 👎

Diese Technik habe ich in meinen Beiträgen bereits mehrfach verwendet und möchte sie daher für diejenigen, denen sie nicht geläufig ist, kurz erläutern.

Oftmals besitzt man Klassen mit zahlreichen Eigenschaften, für die man direkt nach Aufruf des Konstruktors diverse Werte setzen möchte. Es dient dabei meist eher weniger der Übersicht, all diese Eigenschaften bereits im Konstruktor zur Verfügung zu stellen, sofern sie für die Erzeugung des Objektes nicht zwingend erforderlich sind, bzw. bereits entsprechende Standardwerte hierfür besitzen. Für unsere folgenden Beispiele verwenden wir der Übersicht wegen folgende eher überschaubare Klasse:
Klasse "Person"
010203040506070809101112131415161718192021222324
public class Person {    public string FirstName {        get;        set;    }
public string LastName { get; set; }
public int Age { get; set; }

public Person() { }
public Person(string firstName, string lastName) { this.FirstName = firstName; this.LastName = lastName; }}
Davon können wir nun Objekte instanziieren und die Eigenschaften per Konstruktor oder explizit setzen:
Objekt instanziieren und Eigenschaften explizit setzen
01020304050607080910
  // Eigenschaften explizit definierenPerson ich = new Person();
ich.FirstName = "Holger";ich.LastName = "Stehle";ich.Age = 24;
// Eigenschaften per Konstruktor und explizit definierenPerson ich = new Person("Holger", "Stehle");ich.Age = 24;
Es gibt jedoch noch eine andere – wie ich finde sehr komfortable und vor allem übersichtliche – Variante dies zu lösen, und hier kommen die Objektinitalisierer zum Einsatz, die sich im Übrigen auch mit dem Konstruktor kombinieren lassen:
Objekt instanziieren und Eigenschaften per Objektinitialisierer setzen
0102030405060708091011
  // Konstruktor nicht belegenPerson ich = new Person() {    FirstName = "Holger",    LastName = "Stehle",    Age = 24};
// Konstruktor belegenPerson ich = new Person("Holger", "Stehle") { Age = 24};
Eine erweiterte Möglichkeit bieten nun noch die Auflistungsinitialisierer, indem Sie das komfortable Hinzufügen von Werten zu Containern erlauben, welche IEnumerable implementieren. Dies erspart das mehrfache Aufrufen der entsprechenden Add(…)-Methoden zur Initialisierung. Für die folgenden Beispiele verwenden wir dazu generische Listen, welche einmal Zeichenketten und außerdem Objekte unserer Klasse aufnehmen können:
Auflistungsinitialisierung (einfache Zeichenketten)
010203
List<string> animalList = new List<string>() {    "Hund", "Katze"};
Auflistungsinitialisierung (inkl. Objektinitalisierung)
01020304050607080910111213
List<Person> personList = new List<Person>() {    new Person() {        FirstName = "David",        LastName = "Hilbert"    },    new Person("Georg", "Cantor"),    new Person("Bernhard", "Riemann"),    new Person("Leonhard", "Euler"),    new Person() {        FirstName = "Johann",        LastName = "Bernoulli"    }};
Wie im letzten Beispiel ersichtlich, ist also innerhalb eines Auflistungsinitialisierers wiederum die Verwendung von Objektinitalisierern möglich. Ein paar weitere Beispiele finden sich auch noch im MSDN.

Projektverweise

Kategorien / Archiv  |  Übersicht RSS-Feed

Schlagworte

Suche