Neuerungen in C# 3.0
C# in der Version 3.0 (auch bekannt als Orcas) weist einige neue Spracherweiterungen auf, mit denen das Arbeiten signifikant erleichtert wird. Viele der neuen Techniken erlauben eine h�here Abstraktion des Codes was wiederum die Wiederverwendbarkeit steigert. Desweiteren wird eine gr��ere Flexibilit�t geboten die nicht auf Kosten der Typsicherheit geht.
Um die neue Version zu benutzen, ben�tigen Sie Visual Studio 2005 mit der c# 3.0 Erweiterung oder aber Visual Studio 2008. Dort sind bereits alle ben�tigten Bibliotheken vorhanden.
Typinferenz
Bislang musste beim Deklarieren einer Variable immer der passende Typ angegeben werden, der zum Inhalt kompatibel ist.
1: Int n = 5;
2: String x = �Hallo�;
Durch die Einf�hrung des Schl�sselwortes var hat sich das ge�ndert. Sie k�nnen anstelle des Typen nun folgende Schreibweise anwenden:
1: Var n = 5;
2: Var x = �Hallo�;
Auf den ersten Blick erinnert das sehr an die alten COM Zeiten mit dem Variant Datentyp, der zur Laufzeit gebunden wird. Dies hat zu vielen Problemen (gerade beim Endbenutzer) gef�hrt. In Wirklichkeit wird auch bei Verwendung von var die Typsicherheit bewart und nicht zur Laufzeit ausgewertet. Beim Kompilieren des Codes, wird gepr�ft welcher Datentyp am besten zum Inhalt passt und entsprechend verwendet. Im oberen Beispiel w�re die Variable n vom Typ int, die Variable x vom Typ string. Das l�sst sich mit folgendem Codeschnipsel leicht best�tigen:
1: Int x = 5;
2: Var a=4;
3: Var b=�hallo�;
4: Int c = x+a; //funktioniert
5: Int d = x+b; //funktioniert nicht!! Da b vom Typ string ist
Der var-�Typ� kann also dazu verwendet werden eine Variable zu deklarieren ohne den Datentypen selbst zu ermitteln. Das nennte man �Implizit typisierte lokale Variablendeklaration�. Jedoch rate ich davon ab, auf eigene Variablendeklarationen komplett zu verzichten und nur noch mit var zu arbeiten. Ein Nachteil ist, dass die Lesbarkeit des Codes abnimmt, da man nicht mehr auf den ersten Blick sieht um welchen Datentypen es sich handelt. Welche Vorteile sich daraus ergeben und in welchen Bereichen das Schl�sselwort Verwendung findet, werden Sie in den folgenden Abschnitten erkennen. Jedoch hat das var-Schl�sselwort auch Einschr�nkungen. Es darf nur zum deklarieren von lokalen Variablen verwendet werden. R�ckgabewerte oder Parameter f�r Methoden, Klassen-Member, usw. sind nicht erlaubt. Desweiteren darf nicht null zugewiesen werden.
Lambda-Ausdr�cke
Lambda-Ausdr�cke sind zu vergleichen mit Funktionszeigern in c# 2.0. Das folgende einfache Beispiel, soll Ihnen die bisherige Verwendung von Funktionszeigern veranschaulichen:
1: List<string> myNames = new List<string>();
2: myNames.Add("Marcel");
3: myNames.Add("Sarah");
4: myNames.Add("Tobi");
5: myNames.Add("Andi");
6: //Alte Syntax
7: List<string> filtered1 = myNames.FindAll(FindMethod);
8:
9: private static bool FindMethod(string s)
10: {
11: return (s.Length == 6);
12: }
Als erstes wird eine generische Liste mit verschiedenen Namen definiert. Anschlie�end rufen wir die FindAll Methode auf, die einen Zeiger zu einer Funktion verlangt. F�r jedes Element in der Liste wird unsere Methode FindMethod aufgerufen. Das Ergebnis wird in die Variable filtered1 gespeichert. Das gleiche Ergebnis mit deutlich verk�rzter Syntax l�sst sich mit Hilfe von Lambda-Ausdr�cken erzielen.
1: List<string> myNames = new List<string>();
2: myNames.Add("Marcel");
3: myNames.Add("Sarah");
4: myNames.Add("Tobi");
5: myNames.Add("Andi");
6: //Lambda-Ausdruck
7: List<string> filtered2 = myNames.FindAll((s) => s.Length == 6);
Der Unterschied besteht darin, dass die aufzurufende Funktion nun direkt angegeben werden kann. Lassen Sie uns den Ausdruck nochmals genauer anschauen:
FindAll((s) => s.Length == 6)Die Methode FindAll bekommt folgenden Parameter:
(s) => s.Length == 6
(s) ist der Parameter, welcher in der Funktion FindAll als delegate parameter hinterlegt ist. Sollten mehrere Parameter erforderlich sein, k�nnen diese durch kommagetrennt angegeben werden.
=> Das ist der Lambda Operator, der angibt dass auf der rechten Seite nun die aufzurufende Funktion kommt.
s.Length == 6 Ist der eigentliche Inhalt der Funktion. Sie k�nnten nat�rlich auch ein return davor schreiben. Also return(s.Length == 6). Da es sich aber um eine einzelen Zeile handelt, kann man das return und die geschweiften Klammern weglassen.
Wenn man die Lambda-Ausdr�cke das erste Mal benutzt, scheint es etwas kompliziert zu sein. Nach einer kurzen Einarbeitung erkennt man aber deren Vorteil - schneller anonyme Methoden zu schreiben.
Erweiterungsmethoden
Mit dem Konzept der Erweiterungsmethoden lassen sich Klassen ohne Ableitung mit Funktionen erweitern. Im folgenden Beispiel wird der Typ int (ist im Prinzip auch eine Klasse bzw. Struct) durch die Methode AddNumber() erweitert.
1: public static class MyExtensions
2: {
3: public static int AddNumber(this int n, int number)
4: {
5: return (n + number);
6: }
7: }
Im Code kann man die Methode nun sofort verwenden:
1: int myNumber = 5;
2: int y = myNumber.AddNumber(40);
3:
Die Variable y besitzt nun den Wert 45. Da das Prinzip der Erweiterungsmethoden nun gekl�hrt ist, schauen wir uns das Beispiel nochmal genauer an. Um eine Erweiterungsmethode zu schreiben, ist es erfolderlich diese in eine �ffentliche und statische Klasse zu implementieren. Die eigentliche Methode muss ebenfalls �ffentlich und statsisch sein. Um eine Methode nun endg�ltig als Erweiterungsmethode zu kennzeichnen, muss man � wie Ihnen vermutlich schon aufgefallen ist � im ersten Parameter das Schl�sselwort this vor den Datentypen schreiben, den man erweitern m�chte. Das sch�ne an Erweiterungsmethoden ist, dass man auch generics angeben kann. Dies erh�ht die Flexibilit�t enorm. Das folgende Beispiel ist also f�llig legitim:
1: public static class MyExtensions
2: {
3: public static void WriteType<T>(this T obj)
4: {
5: System.Diagnostics.Debug.WriteLine(obj.ToString());
6: }
7: }
Gerade LINQ macht von Erweiterungsmethoden exzessiv Gebrauch um die Lesbarkeit der Query zu optimieren.
Initialisierung von Objekten
Eine weitere syntaktische Neuerung die den Alltag des Programmierers erleichtern soll, ist die neue Art der Objektinitialisierung. Wollte man bislang ein Objekt initialisieren und gleichzeitig Eigenschaften setzen, konnte man nur hoffen das der Konstruktor eine passende �berladung besitzt, der alle gew�nschten Parameter zur Verf�gung stellt. War dies nicht der Fall, musste man nach der Initialisierung die Eigenschaften separat setzen. So wie im folgendem Beispiel zu sehen:
1: Order myOrder = new Order();
2: myOrder.OrderID = 4;
3: myOrder.OrderInfo = "Test";
Ab C# 3.0 lassen sich direkt bei der Initialisierung �ffentliche Eigenschaften setzen.
1: Order myOrder = new Order { OrderID = 4, OrderInfo = "Test" };
Der Compiler verwendet daf�r den Standardkonstruktor und setzt anschliessend die Eigenschaften. Erst wenn alle Eigenschaften gesetzt sind, wird dieses Objekt f�r andere Threads sichtbar. Nat�rlich kann auch zus�tzlich ein Konstruktor verwendet werden (Sofern vorhanden):
1: Order myOrder1 = new Order(5) { OrderInfo = "Test" };
Die Syntax zur Initialisierung kennt praktisch keine Grenzen. Selbst verschachtelte Anweisungen sind m�glich.
1: Order myOrder2 = new Order(5) { OrderInfo = "Test", Details= new OrderDetails{ ID=1, Product="AFKI"}};
Die Klasse Orders besitzt ein weiteres Unterobjekt OrderDetails, welches auf die gleiche Weise Initialisiert werden kann.
Anonyme Typen
Gerade haben wir eine neue M�glichkeit kennengelernt Objekte zu initialisieren. Dabei musste man den Typen der Deklariert werden soll, explizit angeben:
1: Order myOrder = new Order { OrderID = 4, OrderInfo = "Test" };
Mit dem Einsatz von anonymen Typen ist das nicht mehr zwingend notwendig. Mit dieser Anweisung wird ein neuer anonymer Typ erstellt:
1: var myNewType = new { OrderID = 4, OrderInfo = "Test" };
Die Variable myNewType kann sofort (auch mit IntelliSense) verwendet werden.
1: myNewType.OrderID = 5;
Der Kompiler erzeugt beim Kompilieren f�r jedes angegeben Argument eine �ffentliche Eigenschaft und ein privates Feld. Definieren Sie Typen, deren Argumente gleich sind (gleicher Name und gleiche Reihenfolge), wird der anonyme Typ doppelt vergeben
1: var myNewType = new { OrderID = 4, OrderInfo = "Test", this.Text}; //Type: <>f__AnonymousType0`3
2: var myNewType2 = new { OrderID = 5, OrderInfo = "Test", this.Text }; // Type: <>f__AnonymousType0`3
3: var myNewType3 = new { OrderInfo = "Test", OrderID = 5, this.Text }; // Type: <>f__AnonymousType1`3
Da die Argumenten Reihenfolge des letzten Typen anders ist als die beiden oberen, wird ein neuer Typ erstellt. Hier z.B. �<>f__AnonymousType1`3�.
Bei LINQ-Abfragen spielen anonyme Typen eine zentrale Rolle.