Schlagwort: Softwareentwicklung

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.

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.

Notation formaler Ausdrücke 👍 👎

a + b ist ein Term, wobei a und b als Summanden bezeichnet werden. Das sollte soweit jedem Leser dieses Blogs klar sein – mit Grundschulmathematik soll es nun aber gar nicht weitergehen, wir benötigen nur eine gemeinsame Grundlage für den weiteren Teil des Artikels. Smiley: grinning

Einen Bestandteil haben wir bisher jedoch gar nicht näher betrachtet: Das beinahe selbstverständliche Symbol der Addition, das +. Dabei ist das gar nicht so uninteressant. Was genau ist das eigentlich? Nun, auf jeden Fall wird es als Operator bezeichnet. Diese Schreibweise ist uns so vertraut, dass sich die meisten bisher wahrscheinlich gar keine Gedanken zu den Hintergründen einer solchen Schreibweise gemacht haben – und wir auch andere ganz selbstverständlich verwenden.

Tatsächlich handelt es sich nämlich um den in sogenannter Infixnotation geschriebenen Aufruf der Funktion +(a,b); der Name der Funktion lautet also + und Funktionen dieser Darstellung werden üblicherweise in Präfixnotation geschrieben. Dies ist einem aus der Mathematik bei Funktionen wie sin und cos geläufig und selbstverständlich auch als Entwickler der meisten Programmiersprachen und sogar bei Konsoleneingaben.

Präfixnotation findet in der Mathematik meist bei unären Operationen Anwendung, wohingegen bei binären Operationen für gewöhnlich die Infixnotation zum Einsatz kommt. Mehrstellige Operationen werden (auch) in einfacher Funktionsschreibweise notiert.

Bei in Infixnotation geschriebenen Ausdrücken ist unbedingt die Operatorpräzedenz (und ggf. Operatorassoziativität) zu berücksichtigen. Dazu gehört auch der den meisten Lesern sicherlich bekannte Spruch "Punkt vor Strich": Manche Operationen müssen vor anderen ausgewertet werden, um das Ergebnis eindeutig zu halten; im Allgemeinen gilt beispielsweise:

(a + b) * c  ≠  a + (b * c)

Wie dadurch bereits ersichtlich und hoffentlich bekannt, lässt sich eine bestimmte Rangfolge mit Klammerung auch erzwingen. Üblicherweise gilt ohne nähere Spezifikation folgende Rangfolge bei einfachen Rechnungen:
  1. Klammerung
  2. Potenzierung
  3. Multiplikation, Division ("Punktrechnung")
  4. Addition, Subtraktion ("Strichrechnung")
Auch Programmiersprachen (z. B. C#, PHP) besitzen eine solche Rangfolge und unterscheiden sich teilweise.

Um aber noch einmal auf unsere Feststellung der unterschiedlichen Schreibweisen einzugehen: Tatsächlich ist dieses Problem nämlich der Infixnotation geschuldet; Präfixnotation (im Übrigen auch die umgekehrte Variante, die Postfixnotation) kann nämlich klammerfrei erfolgen und ist ganz einfach – wenn auch für die meisten sicherlich eher etwas ungewohnt – auf unser Beispiel der Addition übertragbar:
Addition mit Symbol und Präfixnotation
01
+ a b
Auch "komplexere" (na ja …) Anwendungen stellen kein Problem dar:
Vergleich von Infix- und Präfixnotation
0102030405
  // Infixnotation(a + b) * (c – d)
// Präfixnotation* + a b – c d
Wie man sieht, können hier die Klammern entfallen und man erhält dennoch das erwartete Ergebnis. Diese Darstellungsform vereinfacht im Übrigen das automatisierte Verarbeiten erheblich.

Enumerationen in C# 👍 👎

Während man in anderen Programmiersprachen (wie beispielsweise PHP) eine Sammlung von Konstanten bemühen muss, um zumindest so etwas ähnliches wie einen Aufzählungstypen zu definieren, bietet uns C# hierfür eine komfortable und zugleich typsichere Lösung mittels enum an.

Häufig finden derartige Aufzählungen im Rahmen von Optionen für Methodenaufrufe Verwendung; auch im .NET-Framework begegnet ihr diesen sehr häufig (z. B. bei Dateioperationen mit der Enumeration FileMode). Als Beispiel soll hier das Recht eines Benutzers dienen. Zuerst definieren wir dazu eine entsprechende Enumeration:
Enumeration "Permission" mit den Rechten "Lesen", "Schreiben" und "Löschen"
0102030405
public enum Permission {    Read,    Write,    Delete}
Als nächstes erstellen wir eine (sehr minimalistische) Benutzer-Klasse, welche eine zu unserer Rechte-Enumeration kompatible Eigenschaft besitzt:
Klasse "User" mit Eigenschaft für Benutzerrecht
010203040506
public class User {    public Permission UserPermission {        get;        set;    }}
Jetzt können wir auch schon eine Benutzer-Instanz erzeugen und ein Recht zuweisen, beispielsweise "Lesen":
Benutzer mit Leserecht erzeugen
010203
User du = new User() {    UserPermission = Permission.Read};
Das Prinzip entspricht soweit grundsätzlich also dem Vorgehen wie bei jedem anderen Datentypen (→ allgemeinere Informationen) auch. Insbesondere können wir der Eigenschaft UserPermission z. B. nicht versehentlich einen Wert einer Aufzählung "Farbe" oder "Geschlecht" zuweisen. Nun wäre es noch praktisch, wenn wir auch auf ein spezifisches Benutzerrecht prüfen könnten – also implementieren wir eine entsprechende Methode in der Benutzer-Klasse:
Methode zur Rechteprüfung in "User"
010203
public bool CheckPermission(Permission permission) {    return (this.UserPermission == permission);}
Die Verwendung gestaltet sich nun denkbar einfach (ich schreibe es der Vollständigkeit wegen dennoch auf Smiley: tongue_out):
Rechteprüfung
0102030405
if(du.CheckPermission(Permission.Write)) {    // Recht vorhanden} else {    // Recht NICHT vorhanden}
Soweit können wir glaube ich zufrieden sein. Einen kleinen Schönheitsfehler gibt es jedoch: Wenn wir voraussetzen, dass ein Benutzer mit reinen Schreibrechten noch nichts löschen darf, so müssten wir ihm zwei Rechte zuweisen, wenn er es doch können soll. Wir könnten also eine Rechteliste führen – oder wir bedienen uns der Bitarithmetik und des Flags-Attributs (→ weitere Details zur Verwendung) und bringen beide Rechte in nur einem Feld unter.

Dazu passen wir zuerst einmal unsere Aufzählung an, da wir zu diesem Zwecke Zweierpotenzen als Werte benötigen und geben auch gleich das Attribut mit an. Der späteren Bequemlichkeit wegen, erstellen wir auch noch einen weiteren Wert ReadWrite, welchen wir mit einem bitweisen ODER aus Read und Write definieren:
Enumeration "Permission" mit Attribut und eigenen Werten versehen, sowie Erweiterung um "ReadWrite"
01020304050607
[Flags]public enum Permission {    Read = 1,    Write = 2,    Delete = 4,    ReadWrite = (Read | Write)}
Darüber hinaus müssen wir unsere Prüfmethode geringfügig anpassen:
Methode zur Rechteprüfung in "User" erweitern
010203
public bool CheckPermission(Permission permission) {    return ((this.UserPermission & permission) == permission);}
Nun können wir wieder ein Objekt erzeugen und erhalten die erwarteten Ergebnisse:
Benutzer mit Rechten erzeugen und prüfen
01020304050607080910111213
  // Benutzer mit Lese- und Schreibrecht erzeugenUser du = new User() {    UserPermission = (Permission.Read | Permission.Write)      // – oder auch -    UserPermission = Permission.ReadWrite};
// Rechte prüfen (einige Beispiele)du.CheckPermission(Permission.Read); // truedu.CheckPermission(Permission.Write); // truedu.CheckPermission(Permission.Delete); // falsedu.CheckPermission(Permission.ReadWrite); // truedu.CheckPermission(Permission.Read | Permission.Delete); // false
Eine derartige Vorgehensweise findet auch innerhalb des .NET-Frameworks Verwendung, z. B. bei Dateioperationen mit der Enumeration FileAccess.

Projektverweise

Kategorien / Archiv  |  Übersicht RSS-Feed

Schlagworte

Suche