C# in der Version 3.0 (auch bekannt als „Orcas“) brachte einige neue Spracherweiterungen mit, die das Arbeiten deutlich erleichtern. Viele der neuen Techniken erlauben eine höhere Abstraktion des Codes, was die Wiederverwendbarkeit steigert – ohne dabei die Typsicherheit zu opfern.

Typinferenz

Bislang musste beim Deklarieren einer Variable immer der passende Typ angegeben werden:

int n = 5;
string x = "Hallo";

Mit dem Schlüsselwort var lässt sich das abkürzen:

var n = 5;
var x = "Hallo";

Anders als beim COM-Variant-Datentyp bleibt die Typsicherheit dabei vollständig erhalten – der Typ wird beim Kompilieren ermittelt, nicht zur Laufzeit:

int x = 5;
var a = 4;
var b = "hallo";
int c = x + a; // funktioniert
int d = x + b; // funktioniert nicht – b ist vom Typ string

Diese „implizit typisierte lokale Variablendeklaration“ sollte jedoch nicht die einzige Deklarationsart werden: Sie verringert die Lesbarkeit, da der Datentyp nicht mehr auf den ersten Blick erkennbar ist. Zudem ist var nur für lokale Variablen erlaubt – nicht für Rückgabewerte, Parameter oder Klassenmember – und darf nicht mit null initialisiert werden.

Lambda-Ausdrücke

Lambda-Ausdrücke sind vergleichbar mit Funktionszeigern in C# 2.0:

List<string> myNames = new List<string>();
myNames.Add("Marcel");
myNames.Add("Sarah");
myNames.Add("Tobi");
myNames.Add("Andi");

// Alte Syntax
List<string> filtered1 = myNames.FindAll(FindMethod);

private static bool FindMethod(string s)
{
    return (s.Length == 6);
}

Mit Lambda-Ausdrücken lässt sich dasselbe deutlich kompakter formulieren:

List<string> filtered2 = myNames.FindAll((s) => s.Length == 6);

Der Ausdruck im Detail:

Erweiterungsmethoden

Mit Erweiterungsmethoden lassen sich Klassen ohne Ableitung um Funktionen erweitern:

public static class MyExtensions
{
    public static int AddNumber(this int n, int number)
    {
        return (n + number);
    }
}

Im Code kann die Methode sofort verwendet werden:

int myNumber = 5;
int y = myNumber.AddNumber(40); // y == 45

Eine Erweiterungsmethode muss in einer öffentlichen, statischen Klasse liegen und selbst öffentlich und statisch sein. Das Schlüsselwort this vor dem ersten Parameter markiert den erweiterten Datentyp – auch mit Generics:

public static class MyExtensions
{
    public static void WriteType<T>(this T obj)
    {
        System.Diagnostics.Debug.WriteLine(obj.ToString());
    }
}

Gerade LINQ macht von Erweiterungsmethoden exzessiv Gebrauch, um die Lesbarkeit von Queries zu erhöhen.

Initialisierung von Objekten

Bislang mussten Eigenschaften nach der Objekterstellung einzeln gesetzt werden:

Order myOrder = new Order();
myOrder.OrderID = 4;
myOrder.OrderInfo = "Test";

Ab C# 3.0 lassen sich öffentliche Eigenschaften direkt bei der Initialisierung setzen:

Order myOrder = new Order { OrderID = 4, OrderInfo = "Test" };

// auch zusammen mit einem Konstruktor:
Order myOrder1 = new Order(5) { OrderInfo = "Test" };

// und verschachtelt:
Order myOrder2 = new Order(5) { OrderInfo = "Test", Details = new OrderDetails { ID = 1, Product = "AFKI" } };

Anonyme Typen

Mit anonymen Typen muss der Zieltyp nicht mehr explizit angegeben werden:

var myNewType = new { OrderID = 4, OrderInfo = "Test" };
myNewType.OrderID = 5; // sofort mit IntelliSense nutzbar

Der Compiler erzeugt für jedes Argument eine öffentliche Eigenschaft und ein privates Feld. Typen mit gleichem Namen und gleicher Reihenfolge der Argumente teilen sich denselben anonymen Typ:

var myNewType  = new { OrderID = 4, OrderInfo = "Test", this.Text }; // Type: <>f__AnonymousType0`3
var myNewType2 = new { OrderID = 5, OrderInfo = "Test", this.Text }; // Type: <>f__AnonymousType0`3
var myNewType3 = new { OrderInfo = "Test", OrderID = 5, this.Text }; // Type: <>f__AnonymousType1`3

Bei LINQ-Abfragen spielen anonyme Typen eine zentrale Rolle.