Operatorüberladung in C# 👍 👎

C++-Entwickler werden sich freuen (oder auch nicht Smiley: winking) und PHP-Entwickler wohl größtenteils irritiert sein: C# unterstützt das Überladen von Operatoren für eigene Datentypen. Das ist nun natürlich nicht neu, aber gemeinhin eher selten eingesetzt. Das im Übrigen nicht ganz ohne Grund, aber dazu später mehr.

Was bedeutet das nun konkret? Nun, Operatoren dürften den meisten zumindest aus dem mathematischen Sprachgebrauch bekannt sein: "+" und "-" sind beispielsweise solche Operatoren. Und diese werden auch genau in diesem Sinne bei den meisten Programmiersprachen – so auch bei C# – verwendet, wenn es um das Rechnen mit numerischen Datentypen geht. Unter C# dient "+" darüber hinaus auch der Konkatenierung von Zeichenketten und außerdem dem Hinzufügen von Ereignis-Behandlungsroutinen (und "-" dem Entfernen).

Hier sehen wir schon, dass zumindest manche dieser Operatoren vom Kontext abhängige Bedeutungen besitzen können. Tatsächlich können wir ein solches Verhalten auch für eigene Klassen implementieren und werden dies nun exemplarisch auch angehen.

Insbesondere im mathematischen Bereich, wo Operatoren klar definierte und hinreichend bekannte Semantik besitzen, bieten sich eigene Operatoren an. Wir werden dazu eine einfache Klasse zur Intervallarithmetik – also dem Rechnen mit Intervallen – implementieren, welche uns die beiden binären Operatoren "+" und "-" für Objekte dieses Typs anbietet. Dazu entwickeln wir als erstes eine ganz normale Klasse, ergänzen diese jedoch um zwei spezielle öffentliche, statische Methoden für die Operationen:
Intervall-Klasse mit Operatorüberladung zur Addition und Subtraktion
0102030405060708091011121314151617181920212223242526
public class Interval {    public double X {        get;        set;    }
public double Y { get; set; }

public static Interval operator +(Interval a, Interval b) { return new Interval() { X = (a.X + b.X), Y = (a.Y + b.Y) }; }
public static Interval operator -(Interval a, Interval b) { return new Interval() { X = (a.X – b.Y), Y = (a.Y – b.X) }; }}
Wichtig bei den beiden speziellen Methoden, ist also die Kennzeichnung durch das Schlüsselwort operator, gefolgt vom entsprechenden Symbol.

Die Verwendung ist jetzt denkbar einfach und intuitiv:
Erzeugung zweier Intervalle und Addition dieser zu einem neuen Intervall
01020304050607080910111213
  // Intervalle erzeugenInterval a = new Interval() {    X = 1,    Y = 3};
Interval b = new Interval() { X = 2, Y = 4};
// Intervalle zu neuem Intervall addierenInterval c = (a + b);
Die Subtraktion verhält sich natürlich analog dazu, nur eben mit "-". Intuitiv sollte man beim Thema Operatorüberladung im Übrigen ernst nehmen und nicht nur aus persönlicher Motivation heraus betrachten. Auf jeden Fall sollte man widersprüchliche Definitionen zur allgemeinen Ansicht vermeiden (also: bitte aus "+" kein "-" machen und umgekehrt). Damit würde man nämlich den Vorteil der Übersichtlichkeit wieder völlig zunichtemachen. Insofern solltet ihr bei eigenen Implementierungen immer darauf achten, eine nachvollziehbare Semantik einzuhalten und den Entwickler darüber informieren. Ein sicherlich vorbildliches Beispiel sind die relationalen Operatoren der DateTime-Struktur.

Zum Schluss gilt noch zu berücksichtigen, dass nicht alle Operatoren überladen werden können und relationale Operatoren paarweise überladen werden müssen. Detailliertere Informationen dazu findet ihr selbstverständlich wie immer im MSDN.

TLDs ohne Ende 👍 👎

Ich möchte mich an dieser Stelle gar nicht groß über die neuen TLDs auslassen, deren Nutzen mir großteils ohnehin fraglich erscheint – wahrscheinlich wird das zu großen Teilen lediglich in Geldmacherei münden.

Aber auch die bestehenden TLDs haben bereits ihre Tücken und davon nicht zu knapp. Wie einige von euch sicher bereits wissen werden (→ zu meiner Person), arbeite ich als Softwareentwickler bei einem ISP. Das bedeutet wir gehören zu denen, die Endkunden und auch Unternehmen Domains zur Registrierung anbieten und deren Betreuung übernehmen.

Natürlich bleibt es da nicht aus, dass man auch Anwendungen entwickelt, die auf die Systeme anderer Registrare und Registrierungsstellen (z. B. DENIC für .de-Domains) zugreifen müssen. Nun befindet man sich im IT-Sektor ja eigentlich in einer – zumindest sollte man das meinen – strukturierten Umgebung und das trifft auch auf vieles zu, leider aber nicht auf die Schnittstellen und Formate bei Domain-bezogenen Abfragen.

Wenn man sich beispielsweise daran machen möchte, Whois-Abfragen für diverse Top-Level-Domains ("TLD", z. B. "net") zu implementieren, so hat man es tatsächlich bei den meisten Domainendungen mit völlig unterschiedlichen Ausgaben zu tun, die es anschließend zur eigenen Verarbeitung erst einmal auf ein einheitliches Format zu bringen gilt. Zugegebenermaßen hätte ich von ICANN und Co. an dieser Stelle deutlich mehr Initiative für ein einheitliches Format erwartet, da der aktuelle Zustand die Wart- und Verarbeitbarkeit der Daten außerordentlich und wie ich finde auch völlig unnötig erschwert.

Zu allem Überfluss sind auch noch die Anforderungen und Formalien bei verschiedenen TLDs teilweise völlig unterschiedlich. Das bedeutet, dass man je nach Domainendung auch noch zusätzliche (meist länderspezifische) Daten benötigt. Das beginnt bei der Angabe von Nameservern, geht über Einschränkungen im Domainnamen und endet (nicht wirklich) bei den Angaben zur Person des Inhabers einer Domain.

Erweiterungsmethoden in C# 👍 👎

Die meisten haben sicherlich bereits von den sog. Erweiterungsmethoden unter C# gehört, meinem Eindruck nach haben jedoch deutlich weniger eine konkrete Vorstellung davon oder sie gar bereits selbst verwendet. Dies ist jedoch ganz einfach und kann durchaus nützlich sein, weshalb ich hier eine kleine Einführung geben möchte.

Ganz allgemein lässt sich sagen, dass Typen (sowohl eingebaute, als auch selbst definierte) mittels Erweiterungsmethoden um neue Operationen ergänzt werden können – im Ergebnis also ähnlich zur herkömmlichen Ableitung von einer Klasse, doch dazu später noch ein paar Worte mehr.

Wir möchten in diesem Beitrag den DateTime-Typen insofern erweitern, dass er die komfortable Konvertierung eines DateTime-Objekts zu einem UNIX-Zeitstempel ermöglicht, was insbesondere sinnvoll sein kann, wenn alte (z. B. PHP-basierte) Projekte mit entsprechenden Werten in einer Datenbank auf ASP.NET umgestellt werden sollen.

Um dieses Verhalten zu implementieren ist nicht mehr nötig, als in einer statischen Klasse eine öffentliche statische Methode der gewünschten Funktionalität zu implementieren, welche jedoch einen speziellen ersten Parameter erhält:
Erweiterungsmethode für "DateTime" implementieren
01020304050607
public static class ExtensionMethods {    public static int ToUnixTimestamp(this DateTime dateTime) {        DateTime unixBaseTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
return Convert.ToInt32((dateTime.ToUniversalTime() – unixBaseTime).TotalSeconds); }}
Bei dem bereits angekündigten "speziellen ersten Parameter" handelt es sich also um "this DateTime", wobei das Schlüsselwort this der Hinweis dafür ist, dass der nachfolgende Typ (in diesem Fall DateTime) erweitert werden soll.

Die Verwendung ist anschließend denkbar einfach, da sich derartige Operationen aus Sicht des Entwicklers im weiteren Verlauf wie Instanzmethoden des entsprechenden Objekts verhalten:
Erweiterungsmethode für "DateTime" verwenden
010203040506
  // Zeitstempel für "jetzt"int unixTimestamp = DateTime.Now.ToUnixTimestamp();
// Zeitstempel für beliebiges DateTime-ObjektDateTime birthdate = new DateTime(1988, 1, 29);int unixTimestamp = birthdate.ToUnixTimestamp();
Der erste Parameter (also der mit this) wird dabei übrigens nicht mit angegeben, da er implizit durch die Instanz gegeben ist. Es gilt außerdem noch zu beachten, dass ggf. der Namensraum eurer Erweiterungsmethoden eingebunden werden muss, sofern er von dem des "Einsatzortes" abweicht.

Nun gilt natürlich noch zu klären (s. o.), wie sich dieses Vorgehen zur "normalen" Ableitung verhält. Dies ist tatsächlich gar nicht immer absolut festzumachen, was man im konkreten Fall einsetzen sollte, aus Sicht eines objektorientierten Designs sollte üblicherweise jedoch Vererbung bevorzugt werden, zumal Erweiterungsmethoden nur auf öffentliche Mitglieder zugreifen können und bei Namenskonflikten ggf. das Nachsehen haben. Es gibt jedoch Fälle, wo dies nicht möglich ist: So ist beispielsweise der eingebaute Typ string als sealed markiert, so dass davon nicht geerbt werden kann. In einem solchen Fall kann die Ergänzung per Erweiterungsmethode sinnvoll sein. Auch in unserem Beispiel halte ich den Einsatz von Erweiterungsmethoden für legitim, schon weil ein neuer Typ (etwa "DateTimeWithUnixTimestampCapability"? Smiley: winking) nur begrenzt sinnvoll erscheint.

Möglicherweise benutzt ihr im Übrigen sogar ständig Erweiterungsmethoden, ohne es zu wissen. Microsoft macht davon nahezu exzessiven Gebrauch im Zusammenhang mit LINQ, um euch so komfortable Operationen wie Count(…) und Where(…) auf beliebigen Objekten anbieten zu können, die IEnumerable implementieren.

Datenfelder in C# und PHP 👍 👎

Da ich regelmäßig – insbesondere von PHP-Entwicklern – gefragt werde, wie sich das dort allseits beliebte Array in C# verwenden lässt, möchte ich dazu eine kleine Gegenüberstellung präsentieren.

Zuerst einmal sollte festgestellt werden, dass Arrays unter PHP sehr vielseitig eingesetzt werden, wofür andere Programmiersprachen (wie beispielsweise C#) jeweils eigens optimierte Datenstrukturen vorsehen. Nicht zu vergessen ist natürlich ganz allgemein die unter C# strengere Typisierung.

Wir beginnen die Beispiele demnach also erst einmal mit einfachen Listen und gehen schließlich auch auf mehrdimensionale und assoziative Felder ein.
Liste von Zeichenketten: PHP
010203
$data = ["Ab", "Cd", "Ef"];
$data[] = "Yz";
Liste von Zeichenketten: C# (nativ)
01
string[] data = {"Ab", "Cd", "Ef"};
Es gibt jedoch noch eine elegantere Lösung: Generische Container. Hierbei handelt es sich um vom konkreten Datentyp abstrahierte Mechanismen zur Sammlung und Verwaltung von Daten. Sehen wir uns also das o. g. Beispiel mit Hilfe der generischen List-Klasse in C# an:
Liste von Zeichenketten: C# (generisch)
010203
List<string> data = new List<string>() {"Ab", "Cd", "Ef"};
data.Add("Yz");
Durch den Aufruf entsprechender Instanzmethoden können wir dadurch sehr komfortabel neue Daten hinzufügen, den Inhalt sortieren und natürlich auch per LINQ darauf operieren. Dank Implementierung eines sog. Indexers ist im Übrigen auch bei dieser Variante der gewohnte null-basierte Zugriff per Index z. B. per data[0] möglich.

Gehen wir nun also einen Schritt weiter und stellen mehrdimensionale Felder gegenüber:
Mehrdimensionales Feld: PHP
010203040506
$data = [    ["Ab", "Cd"],    ["Ef", "Gh"]];
$data[] = ["Wx", "Yz"];
Mehrdimensionales Feld: C# (nativ)
01020304
string[,] data = {    {"Ab", "Cd"},    {"Ef", "Gh"}};
Mehrdimensionales Feld: C# (generisch)
010203040506
List<List<string>> data = new List<List<string>>() {    new List<string>() {"Ab", "Cd"},    new List<string>() {"Ef", "Gh"}};
data.Add(new List<string> {"Wx", "Yz"});
Zum Schluss sollen natürlich noch assoziative Felder erwähnt werden, welche wir in C# mit Hilfe eines Dictionary umsetzen:
Assoziatives Feld: PHP
010203040506
$data = [    'FirstName' => "Holger",    'LastName' => "Stehle"];
$data['City'] = "Berlin";
Assoziatives Feld: C#
0102030405060708
Dictionary<string,string> data = new Dictionary<string,string>() {    {"FirstName", "Holger"},    {"LastName", "Stehle"}};
data.Add("City", "Berlin"); // – oder auch -data["City"] = "Berlin";
Zum Durchlaufen eines solchen Feldes in C# bietet sich die Verwendung der KeyValuePair-Struktur an:
Assoziatives Feld durchlaufen: C#
01020304
foreach(KeyValuePair<string,string> entry in data) {    // entry.Key: "FirstName", "LastName" und "City"    // entry.Value: "Holger", "Stehle" und "Berlin"}
Das soll es für den Moment auch erst einmal gewesen sein, zukünftig möchte ich jedoch noch ein paar derartiger Gegenüberstellungen durchführen. Insbesondere auch auf die generische Entwicklung unter C# werde ich noch gesondert in weiteren Beiträgen eingehen.

RSS-Feeds bei Links 👍 👎

Auf der Seite "Links" sind nun die RSS-Feeds der Seiten verlinkt, sofern verfügbar.

Projektverweise

Kategorien / Archiv  |  Übersicht RSS-Feed

Schlagworte

Suche