Implizite Typisierung in C# 👍 👎

C# ist und bleibt zwar eine grundsätzlich statisch typisierte Programmiersprache, erlaubt jedoch auch die implizite Typisierung mit Hilfe des var-Schlüsselwortes:
Verwendung
01
var age = 24;  // äquivalent zu "int age = 24"
Der konkrete Typ wird dabei implizit durch den Wert auf der rechten Seite bestimmt. Dies kann den Quelltext besonders bei komplexen Strukturen (z. B. im Hinblick auf generische Programmierung) etwas kompakter gestalten, je nach Gewohnheit aber auch etwas schwerer zu verstehen sein. Es bleibt wie so oft dem Entwickler (bzw. den Richtlinien des Teams) überlassen, wie damit umzugehen ist.

Wichtig zu verstehen ist in jedem Fall, dass var in diesem Fall nur ein syntaktisches Hilfsmittel ist und keine dynamische Typisierung eingeführt wird (dazu in einem späteren Artikel mehr). Das bedeutet insbesondere, dass unserem "age" später nicht etwa eine Zeichenkette zugewiesen werden kann.

Zwingend notwendig ist diese Vorgehensweise jedoch bei anonymen Typen, da der Typ keinen (bzw. lediglich einen internen, dem Compiler bekannten) Typenbezeichner besitzt:
Verwendung bei anonymen Typen
0102030405
var ich = new {    FirstName = "Holger",    LastName = "Stehle",    Age = 24};
Auch im Rahmen von LINQ-Abfrageausdrücken kann dieses Vorgehen beispielsweise bei der Projektion von Daten aus einer Liste mittels Select(…) notwendig sein (aus ähnlichen Gründen wie im Beispiel zuvor).

Optionale und benannte Parameter in C# 👍 👎

Wie viele andere Programmiersprachen (z. B. PHP) unterstützt auch C# seit einiger Zeit optionale Parameter. Die Verwendung ist denkbar einfach:
Optionale Parameter
01020304050607080910
  // Kreisfläche berechnenpublic double GetCircleArea(double radius = 1) {    return (Math.PI * (radius * radius));}

double circleArea = GetCircleArea(5); // Kreisfläche für Radius "5"
double circleArea = GetCircleArea(1); // Kreisfläche für Radius "1"double circleArea = GetCircleArea(); // Kreisfläche für Radius "1"
Der beim letzten Aufruf weggelassene Parameterwert wird dabei implizit durch die Zuweisung des Standardwertes der Definition (radius = 1) gesetzt, um den Flächeninhalt des Einheitskreises zu erhalten. Aus naheliegenden Gründen sind optionale Parameter hinter nicht-optionalen Parametern zu definieren. Ein ähnliches Verhalten lässt sich selbstverständlich auch durch das Überladen der Methode erreichen, was jedoch etwas aufwändiger ist und zumindest in unserem Beispiel nicht ganz so intuitiv erscheint:
Optionale Parameter (per Methodenüberladung)
0102030405060708
  // Kreisfläche berechnenpublic double GetCircleArea(double radius) {    return (Math.PI * (radius * radius));}
public double GetCircleArea() { return this.GetCircleArea(1);}
Der Aufruf kann nun jeweils analog zu unserem vorherigen Beispiel erfolgen.

Darüber hinaus unterstützt C# jedoch auch benannte Parameter. Damit ist es nicht notwendig, die korrekte Position eines Parameters zu verwenden; diese werden stattdessen über ihren Namen angesprochen, was insbesondere im Zusammenhang mit (mehreren) optionalen Parametern sinnvoll sein kann:
Benannte Parameter
0102030405060708091011
  // Flächeninhalt berechnenpublic double GetArea(double height = 1, double width = 1) {    return (height * width);}

double area = GetArea(); // Höhe: 1, Breite: 1double area = GetArea(2); // Höhe: 2, Breite: 1double area = GetArea(3,4); // Höhe: 3, Breite: 4
double area = GetArea(width: 5); // Höhe: 1, Breite: 5 (benannter Parameter)
Es genügt dazu also, ganz einfach den Namen des Parameters, gefolgt von einem ":" (Doppelpunkt) und dem Wert anzugeben. Zu beachten gilt, dass benannte Parameter zwar auf per Position festgelegte Parameter folgen können, der umgekehrte Fall jedoch nicht unterstützt wird.

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.

Projektverweise

Kategorien / Archiv  |  Übersicht RSS-Feed

Schlagworte

Suche