myCSharp.de - DIE C# und .NET Community
Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 
 | Suche | FAQ

» Hauptmenü
myCSharp.de
» Startseite
» Forum
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Suche
   » Plugin für Firefox
   » Plugin für IE
   » Gadget für Windows
» Regeln
» Wie poste ich richtig?
» Datenschutzerklärung
» wbb-FAQ

Mitglieder
» Liste / Suche
» Stadt / Anleitung dazu
» Wer ist wo online?

Angebote
» ASP.NET Webspace
» Bücher
» Zeitschriften
   » dot.net magazin
» Accessoires

Ressourcen
» guide to C#
» openbook: Visual C#
» openbook: OO
» .NET BlogBook
» MSDN Webcasts
» Search.Net

Team
» Kontakt
» Übersicht
» Wir über uns
» Bankverbindung
» Impressum

» Unsere MiniCity
MiniCity
» myCSharp.de Diskussionsforum
Du befindest Dich hier: Community-Index » Diskussionsforum » Knowledge Base » Artikel » [Artikel] Delegaten, anonyme Methoden, Lambda-Ausdrücke & Co.
Letzter Beitrag | Erster ungelesener Beitrag Druckvorschau | Thema zu Favoriten hinzufügen

Antwort erstellen
Zum Ende der Seite springen  

[Artikel] Delegaten, anonyme Methoden, Lambda-Ausdrücke & Co.

 
Autor
Beitrag « Vorheriges Thema | Nächstes Thema »
dN!3L dN!3L ist männlich
myCSharp.de-Poweruser/ Experte

images/avatars/avatar-2985.png


Dabei seit: 13.08.2004
Beiträge: 2.886


dN!3L ist offline

[Artikel] Delegaten, anonyme Methoden, Lambda-Ausdrücke & Co.

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Da die Verwendung von Delegaten (und Events) für Anfänger oft schwer zu verstehen und Lambda-Ausdrücke selbst für einige Fortgeschrittene verwirrend sind, soll dieser Artikel einen kleinen Überblick über die Konzepte Delegat, anonyme Methode und Lambda-Ausdruck geben.

Fangen wir also damit an, wozu man sowas überhaupt braucht. Schauen wir uns also einmal das  Strategie-Entwurfsmuster an, das dazu dient, eine Familie von austauschbaren Algorithmen zu definieren: Mithilfe von Strategieobjekten kann man dort bestimmte Codeteile unabhängig von speziellen Algorithmen machen. Diesen Teil des Strategie-Entwurfsmusters (es fehlt z.B. der Kontext) möchte ich für ein einfaches Beispiel aufgreifen, das im folgenden Verlauf immer wieder Verwendung finden soll:

C#-Code (Beispiel 1 - Strategieobjekte zum Auslagern von speziellen Implementierungen):
public interface IStrategy
{
    string GetText(string name);
}

public class HelloStrategy : IStrategy
{
    public string GetText(string name)
    {
        return "Hello, "+name;
    }
}

public class GoodbyeStrategy : IStrategy
{
    public string GetText(string name)
    {
        return "Goodbye, "+name;
    }
}

public class Executor
{
    public void Main()
    {
        IStrategy helloStrategy = new HelloStrategy();
        IStrategy goodbyeStrategy = new GoodbyeStrategy();

        this.Display(helloStrategy);
        this.Display(goodbyeStrategy);
    }

    private void Display(IStrategy strategy)
    {
        Console.WriteLine(strategy.GetText("World"));
    }
}

Der Algorithmus zur Ermittlung des anzuzeigenden Textes wurde ausgelagert – die Methode Display ist somit unabhängig vom Algorithmus. Zudem ist der Algorithmus austauschbar (HelloStrategy vs. GoodbyeStrategy). Die Ausgabe ist übrigens

	Hello, World
	Goodbye, World

Doch was hat das ganze nun mit Delegaten, anonymen Methoden und Lambda-Ausdrücken zu tun? Nun, einfach gesagt macht man mit Delegaten & Co. genau das, nur spart man sich die Implementierung einer speziellen Strategie-Klasse – wie genau zeigen die folgenden Abschnitte.



Delegaten
Seit der Version 1.1 des .NET-Frameworks werden  Delegaten unterstützt. In Sprachen wie C, C++ oder Pascal würde man Delegaten als Funktionszeigern kennen – allerdings sind Delegaten streng typisiert. Man hat also einen Datentyp, der Methoden aufnehmen kann.

Zur Veranschaulichung soll Beispiel 1 so abgeändert werden, dass statt der Strategieobjekte Delegaten verwendet werden. Die jeweilige Methode zur Ermittlung des anzuzeigenden Textes soll also direkt übergeben werden können, ohne dass man explizit die Schnittstelle IStrategy und die Methode GetText implementieren muss. Dazu muss man zunächst - aufgrund der strengen Typisierung - einen Delegatentyp für diese Methode anlegen. Dieser definiert die Signatur einer Methode (also die Typen und Reihenfolge der Parameter) und den Typ des Rückgabewertes. Die Namen des Delegaten und der Parameter sind dabei unwichtig (schon einmal ein Vorteil gegenüber von Strategieobjekten). Zurück zum Beispiel: Die Methode GetText hat den Rückgabetyp string und nimmt einen Parameter ebenfalls vom Typ string entgegen. Die Delegat-Definition sieht dann folgendermaßen aus:

C#-Code (Delegatdefinition):
delegate string GetTextDelegate(string name);

Wie man sieht, werden Delegatendefinitionen mit dem Schlüsselwort  delegate eingeleitet. Danach folgt der Rückgabetyp (string), gefolgt vom Namen des Delegatentyps. Zuletzt kommt dann – analog zu Methodendefinitionen – die Parameterliste.

Als nächstes nehmen wir uns die Methode Display vor: Diese soll jetzt nicht mehr ein Objekt vom Typ IStrategy entgegennehmen, sondern kann direkt ein Objekt vom Typ GetTextDelegate benutzen. Da dieses Parameterobjekt dann direkt eine Methode repräsentiert, geschieht der Aufruf jetzt durch getTextDelegate("World") (statt strategy.GetText("World")).

Beim Aufruf der Display-Methode kann jetzt direkt die Methode GetHelloText als Referenz übergeben werden (was durch eine implizite Konvertierung ein GetTextDelegate-Objekt erstellt; alternativ kann auch der GetTextDelegate-Konstruktor aufgerufen werden). Wie bereits erwähnt, ist der Name der Methode und die Namen der Parameter dabei unwichtig – die Signatur muss passen (Rückgabe- und Parametertypen, siehe auch Abschnitt "Kovarianz und Kontravarianz"). Das überarbeitete Beispiel 1 sieht jetzt also folgendermaßen aus:

C#-Code (Beispiel 2 - Delegaten):
public class Executor
{
    public delegate string GetTextDelegate(string name);

    public string GetHelloText(string name)
    {
        return "Hello, "+name;
    }

    public string GetGoodbyeText(string name)
    {
        return "Goodbye, "+name;
    }

    public void Main()
    {
        GetTextDelegate getHelloText = this.GetHelloText;
        GetTextDelegate getGoodbyeText = this.GetGoodbyeText;

        this.Display(getHelloText);
        this.Display(getGoodbyeText);
    }

    private void Display(GetTextDelegate getTextDelegate)
    {
        Console.WriteLine(getTextDelegate("World"));
    }
}

Kovarianz und Kontravarianz
 Kovarianz und Kontravarianz erlauben es, dass die Signatur des Delegattypen und die der Methode nicht exakt übereinstimmen müssen. Durch Kovarianz kann einem Delegat eine Methode zugewiesen werden, dessen Rückgabetyp eine Spezialisierung des Rückgabetyps des Delegaten ist. Hat der Delegat beispielsweise einen Rückgabewert vom Typ  Stream, kann ihm auch eine Methode zugewiesen werden, die den Rückgabetyp  FileStream hat. Kontravarianz ermöglicht es dagegen, dass die Parametertypen der Methode eine Generalisierung des entsprechenden Parametertypen aus der Delegatdefinition ist. Hat beispielsweise der Delegat einen Parameter vom Typ FileStream, kann ihm eine Methode zugewiesen werden, dessen entsprechender Parameter den Typ Stream hat.

Beim Thema "Rückgabe- und Parametertypen" sollte nicht unerwähnt bleiben, dass bei Delegaten auch  Generika verwendet werden können.


Multicastdelegaten & Ereignisse
Eine nützliche Eigenschaft von Delegaten ist, dass deren Operator + überladen ist, sodass mehrere Delegaten  miteinander kombiniert werden können. Am besten macht das ein Beispiel klar (etwas abgewandelt aus der  MSDN-Doku):

C#-Code (Multicastdelegaten):
public delegate void DoSomethingDelegate(string value);

public void Hello(string value)
{
    Console.WriteLine("Hello, "+value);
}

public void Goodbye(string value)
{
    Console.WriteLine("Goodbye, "+value);
}

public void Main()
{
    DoSomethingDelegate    a = this.Hello;
    DoSomethingDelegate b = this.Goodbye;
    DoSomethingDelegate c = a + b;
    DoSomethingDelegate d = c - a;

    a("A");
    // > "Hello, A"
    b("B");
    // > "Goodbye, B"
    c("C");
    // > "Hello, C"
    //   "Goodbye, C"
    d("D");
    // > "Goodbye, D"
}

Der Effekt beim Aufruf von c ist, dass sowohl Hello als auch Goodbye aufgerufen/ausgeben werden – denn c besteht aus a (Aufruf von Hello) plus b (Aufruf von b). Man kann sich (Muticast-)Delegaten also sogar als Liste von Referenzen auf Methoden vorstellen. Daher ergibt der Aufruf von d auch (nur) ein Goodbye (b), da a aus c (bestehend aus a und b) wieder entfernt wurde.

Ein spezieller Typ von Multicastdelegaten sind  Ereignisse ("events"). Diese werden durch das  event-Schlüsselwort gekennzeichnet und können dann nur in der Klasse oder Struktur aufgerufen werden, in der sie auch deklariert wurden.  Ereignishandler sind nichts weiter als Methoden, die dann durch den Delegaten aufgerufen werden. Das Abonnieren von Ereignissen mittels += bedeutet nichts weiter, als das eine (Ereignisbehandlungs-)Methode dem Multicastdelegaten hinzugefügt wird.



Anonyme Methoden
Wie man eventuell schon am Namen erraten kann, sind  anonyme Methoden solche, die keinen Namen haben. Einige werden sich jetzt fragen, "Häh, Methoden ohne Namen?". Ja das geht - allerdings nur im Zusammenhang mit Delegaten. Im Beispiel 2 haben wir Delegaten erstellt, indem wir ihnen  benannte Methoden zugewiesen haben. Ab C# 2.0 ist es zudem möglich, den Methodenrumpf der referenzierten Methode direkt "vor Ort" (bei der Delegatenerstellung) anzugeben. Da eine solche Methode nur über den Delegaten aufgerufen werden kann, braucht sie auch keinen Namen - daher "anonyme Methoden". Zu Veranschaulichung ändern wird Beispiel 2 so, dass anonyme Methoden verwendet werden. Beispielsweise erstellen einen neuen Delegaten nicht durch new GetTextDelegate(this.GetHelloText) (benannte Methode), sondern folgendermaßen (anonyme Methode; Methodenimplementierung "direkt vor Ort"):

C#-Code (Anonyme Methode):
GetTextDelegate getTextDelegate =
    new GetTextDelegate(
        delegate(string name)
        {
            return "Hello, "+name;
        });

Oder auch kürzer:

C#-Code (Anonyme Methode):
GetTextDelegate getTextDelegate =
    delegate(string name)
    {
        return "Hello, "+name;
    };

Außer dem Schlüsselwort  delegate statt einem Methodennamen und der fehlenden Angabe des Rückgabetypen gibt es erst einmal keine Unterschiede zu normalen benannten Methoden. Beispiel 2, nur mit anonymen Methoden, sieht dann folgendermaßen aus:

C#-Code (Beispiel 3 - Anonyme Methoden):
public class Executor
{
    public delegate string GetTextDelegate(string name);

    public void Main()
    {
        GetTextDelegate getHelloText = delegate(string name) { return "Hello, "+name; };
        GetTextDelegate getGoodbyeText = delegate(string name) { return "Goodbye, "+name; };

        this.Display(getHelloText);
        this.Display(getGoodbyeText);
    }

    private void Display(GetTextDelegate getTextDelegate)
    {
        Console.WriteLine(getTextDelegate("World"));
    }
}

Ein interessantes Feature von anonymen Methoden ist außerdem, dass man die Parameterliste auch komplett weglassen kann - wenn man die Parameter eh nicht benutzt, kann man sich so Schreibarbeit sparen. So kann man beispielsweise

C#-Code (anonyme Methode):
button.Click += delegate(object sender,EventArgs e)
                {
                    MessageBox.Show("Klick");
                };

vereinfachen zu

C#-Code (anonyme Methode ohne Parameterliste):
button.Click += delegate
                {
                    MessageBox.Show("Klick");
                };

Closures
Closures sind  laut Wikipedia "eine Programmfunktion, die beim Aufruf ihren Definitionskontext reproduziert, selbst wenn dieser Kontext außerhalb der Funktion schon nicht mehr existiert." Auch hier soll wieder ein Beispiel der besseren Veranschaulichung dienen:

C#-Code (Closures):
public void Main()
{
    string value = "Hello, ";
    GetTextDelegate getText = delegate(string name)
                    {
                        return value+name;
                    });
    value = "Goodbye, ";
    Console.WriteLine(getText("World"));
}

Der aufmerksame Leser wird sicher merken, dass value zwar in der anonymen Methode benutzt wird, aber in dessen Kontext gar nicht definiert wurde. Solche Variablen werden auch als äußere Variablen bezeichnet (im Gegensatz zu lokalen Variablen). "OK, nettes Feature" wird sich Manchereiner denken, aber das Beste kommt noch: Was wird denn auf der Konsole ausgegeben? Richtig, "Goodbye, World". Auf solche Sachen muss man Acht geben, wenn man anonyme Methoden benutzt. Um solches Verhalten besser verstehen zu können, hilft es, zu wissen, wie anonyme Methoden eigentlich implementiert sind.

Anonyme Methoden unter der Haube
Anonyme Methoden sind eigentlich nur Syntaxzucker. In Wirklichkeit generiert der Compiler automatisch eine Klasse, legt dort eine benannte Methode an und benutzt diese dann. Ebenso werden die äußeren Variablen als Klassenvariablen angelegt. Der Quelltext würde vom Compiler folgendermaßen übersetzt werden (Namen vereinfacht):

C#-Code (vom Compiler generierter Code, um anonyme Methoden zu realisieren):
[CompilerGenerated]
internal sealed class GeneratedClass
{
    public string value;

    public string AnonymousMethod(string name)
    {
        return (this.value + name);
    }
}

public void Main()
{
    GeneratedClass generatedClass = new GeneratedClass();
    generatedClass.value = "Hello, ";
    GetTextDelegate getText = new GetTextDelegate(generatedClass.AnonymousMethod);
    generatedClass.value = "Goodbye, ";
    Console.WriteLine(getText("World"));
}

Man muss sich unbedingt bewusst sein, dass sich die Werte der äußeren Variablen zwischen der Deklaration und der Ausführung eines Delegaten durchaus geändert haben können. Es wird immer der Wert verwendet, den die äußere Variable zur Ausführungszeit hat (bzw. in ihrem Kontext zuletzt hatte).
Eine "beliebte" Fehlerquelle ist beispielsweise die Verwendung der Laufvariablen als äußere Variable für Delegatsdeklarationen innerhalb einer for(each)-Schleife:

C#-Code (foreach-Laufvariable als äußere Variable - Ergebnis dürfte anders sein, als erwartet):
List<Func<string>> functions = new List<Func<string>>();

foreach (int value in new int[] { 1,2,3,4,5 })
    functions.Add(delegate() { return value.ToString(); });

foreach (Func<string> func in functions)
    Console.Write(func()+" ");

Die Ausgabe dürfte etwas unerwartet ausfallen: "5 5 5 5 5 ". Der Grund für dieses Verhalten ergibt sich aus dem Kasten "Anonyme Methoden unter der Haube": Für jeden Schleifendurchlauf wird ein und dasselbe Exemplar der generierten Klasse verwendet - und für die äußere Variable letztendlich den als letztes zugewiesenen Wert.
Lösen kann man das Problem, indem man eine äußere Variable benutzt, die nur innerhalb eines Schleifendurchlaufs gültig ist:

C#-Code (Wert einer Laufvariable als äußere Variable benutzen):
foreach (int value in new int[] { 1,2,3,4,5 })
{
    int i = value;
    functions.Add(delegate() { return i.ToString(); });
}

Hier ist die Ausgabe "1 2 3 4 5". Anders als beim "normalen" Variablen macht es bei äußeren Variablen also durchaus einen Unterschied, ob sie innerhalb oder außerhalb einer Schleife deklariert werden!




Lambda-Ausdrücke
Neben der Fähigkeit, Ausdrucksbaumstrukturen erstellen zu können (später dazu mehr), ist es mit  Lambda-Ausdrücken möglich, (anonyme) Delegatendefinitionen noch kürzer zu gestalten. Nehmen wir als Beispiel folgende anonyme Methode:

C#-Code (Anonyme Methode):
delegate(string name)
{
    return "Hello, "+name;
}

Um daraus einen Lambda-Ausdruck zu machen, löschen wir das delegate-Schlüsselwort und fügen einen  =>-Operator (lies "goes to") hinter der Parameterliste ein (wenn es keine Parameter gibt, ist ein leeres Klammernpaar zu benutzen). Fertig ist der erste Lambda-Ausdruck:

C#-Code (Lambda-Ausdruck):
(string name) =>
{
    return "Hello, "+name;
}

Die obige Form mit geschweiften Klammern wird auch Anweisungslambda genannt.
Aber das war noch längst nicht alles: Da es ja Kontravarianz gibt und der Compiler zudem den Typen herleiten kann, kann man auch die Parametertypen weglassen:

C#-Code (Lambda-Ausdruck):
(name) => { return "Hello, "+name; }

Da wir außerdem nur einen einzigen Parameter haben, brauchen wir die runden Klammern auch nicht mehr. Macht dann:

C#-Code (Lambda-Ausdruck):
name => { return "Hello, "+name; }

Unser Lambda-Ausdruck hat außerdem noch die Eigenschaft, dass die rechte Seite nur aus einem einzigen Ausdruck besteht. Damit erfüllt er die Struktur eines Ausdruckslambdas (im Gegensatz zum Anweisungslambda). Daher können wir die geschweiften Klammer und das Semikolon auch weglassen. Da es sich um eine Rückgabeanweisung handelt, kann man (bzw. muss dann sogar) das return-Schlüsselwort ebenfalls entfernen. Das macht dann zusammen:

C#-Code (Lambda-Ausdruck):
name => "Hello, "+name

Beispiel 3 mit "langen" Lambda-Ausdrücken sieht dann folgendermaßen aus:

C#-Code (Beispiel 4 - Lambda-Ausdrücke):
public class Executor
{
    public delegate string GetTextDelegate(string name);

    public void Main()
    {
        GetTextDelegate getHelloText = (string name) => { return "Hello, "+name; };
        GetTextDelegate getGoodbyeText = (string name) => { return "Goodbye, "+name; };

        this.Display(getHelloText);
        this.Display(getGoodbyeText);
    }

    private void Display(GetTextDelegate getTextDelegate)
    {
        Console.WriteLine(getTextDelegate("World"));
    }
}

Völlig verkürzt sieht der Spaß dann so aus:

C#-Code (Beispiel 5 - noch kürzere Lambda-Ausdrücke):
public class Executor
{
    public delegate string GetTextDelegate(string name);

    public void Main()
    {
        GetTextDelegate getHelloText = name => "Hello, "+name;
        GetTextDelegate getGoodbyeText = name => "Goodbye, "+name;

        this.Display(getHelloText);
        this.Display(getGoodbyeText);
    }

    private void Display(GetTextDelegate getTextDelegate)
    {
        Console.WriteLine(getTextDelegate("World"));
    }
}

Hinweis
Lambda-Ausdrücke sind ein reines Compilerfeature, dass seit Visual Studio 2008 verfügbar ist. Das heißt, dass man zum Beispiel auch in Anwendungen für das .NET Framework 2.0 Lambda-Ausdrücke verwenden kann (sofern man sie nicht in Verbindung mit Ausdrucksbaumstrukturen verwendet, welche erst ab dem .NET-Framework 3.5 verfügbar sind).

Funktionen und Aktionen
Wer hätte es gedacht, den Quelltext aus Beispiel 5 bekommt man noch kleiner. Denn damit man nicht dauernd explizit Delegattypen definieren muss, gibt dafür schon etwas Vorgefertigtes. Im Namensraum  System (Assembly System.Core) existieren dafür (ab Framework-Version 3.5) die Func- und Action-Delegate mit ihren diversen generischen Überladungen.

Action-Delegaten sind für Lambda-Ausdrücke ohne Rückgaben. Für einen Lambda-Ausdruck ohne Parameter verwendet man die  Action-Delegaten, für solche mit einem Parameter  Action<T>, für zwei Parameter  Action<T1,T2>, für drei  Action<T1,T2,T3> und für vier  Action<T1,T2,T3,T4>. Hier ein Beispiel für eine Aktion mit zwei Parametern:

C#-Code (Aktion mit zwei Parametern):
Action<string,int> action = (value,count) =>
    {
        for (int i=0;i<count;i++)
            Console.WriteLine(value);
    };

action("Hello, World",10);

Ein Lambda-Ausdruck ohne Parameter sieht dann übrigens folgendermaßen aus:

C#-Code (Aktion/Lambdaausdruck ohne Parameter):
Action action = () => Console.WriteLine("Hello, World");
action();

Für Lambda-Ausdrücke mit einem Rückgabewert (="Funktion") gibt es die  Func-Delegaten. Entprechend zu den Action-Delegaten gibt es wieder Überladungen für bis zu  vier Parametern. Der letzte generische Typ ist dabei immer der des Rückgabewertes. So zum Beispiel eine Funktion mit  zwei Parametern:

C#-Code (Funktion mit zwei Parametern):
Func<string,int,char> func = (value,index) =>
    {
        return value[index];
    };
char firstChar = func("Hello, World",0);

Benutzt man die entsprechenden Func-Delegaten, sieht Beispiel 5 folgendermaßen aus:

C#-Code (Beispiel 6 - Lambda-Ausdrücke und Func-Delegaten):
public class Executor
{
    public void Main()
    {
        Func<string,string> getHelloText = name => "Hello, "+name;
        Func<string,string> getGoodbyeText = name => "Goodbye, "+name;

        this.Display(getHelloText);
        this.Display(getGoodbyeText);
    }

    private void Display(Func<string,string> func)
    {
        Console.WriteLine(func("World"));
    }
}

Verwendung vom .NET-Framework 2.0
Wie bereits geschrieben, können Lambda-Ausdrücke auch mit dem .NET-Framework 2.0 verwendet werden. Allerdings sind darin die Action- und Func-Delegaten noch nicht enthalten (bzw. lediglich der  Action<T>-Delegat).
Je nach Anwendungsfall bieten sich aber z.B. auch die vorgefertigten  Predicate<T>-,  Comparison<T>-,  Converter<TIn,TOut>- oder  EventHandler<TEventArgs>-Delegaten aus der mscorlib.dll für eine Verwendung in .NET 2.0 an.

LINQ mit Lambda-Ausdrücken verstehen
Mit den bisherigen Informationen haben wir eigentlich auch schon alles zusammen, um  LINQ-Abfrageausdrücke (in Lambda-Notation) verstehen zu können. Nehmen wir als Beispiel folgenden Ausdruck (persons ist eine Liste mit Person-Objekten):

C#-Code (LINQ-Abfrageausdruck in Lambdanotation (automatische Typherleitung)):
var query = persons.Where(p => p.Age>50).Select(p => p.Name);

Zunächst sollte noch erwähnt werden, dass Where und Select generische Typparameter haben, die aber vom Compiler automatisch hergeleitet werden können. Vollständig ausgeschrieben sieht die Abfrage dann so aus:

C#-Code (LINQ-Abfrageausdruck in Lambdanotation (explizite Typangabe)):
IEnumerable<string> query = persons.Where<Person>(p => p.Age>50).Select<Person,string>(p => p.Name);

Der Ablauf dafür ist folgender: Angefangen wird mit der Menge persons, die als Eingabemenge für Where dient. Where wiederum erzeugt eine spezielle Ausgabemenge, die nun für Select als Eingabemenge dient. Schließlich enthält query die Menge, die Select als Ausgabe liefert. Es gilt jetzt also, herauszufinden, was die einzelnen Methoden als Ausgabemenge produzieren.
Fangen wir mit Where an: Where führt eine Selektion der Elemente aus persons aus. Der Algorithmus, welche Objekte selektiert werden, wurde dabei ausgelagert und muss vom Programmierer übergeben werden. Als Parameter wird also eine Methode erwartet, die ein Person-Objekt entgegennimmt und einen Wahrheitswert zurückgibt, der angibt, ob das jeweilige Element in die Ausgabemenge kommt oder nicht. Im Beispiel wird als Parameter also eine Funktion Func<Person,bool> übergeben, die nur true zurück gibt, wenn das Alter der übergebenen Person (p) größer als 50 ist.
Beim Select wurde wiederum der Algorithmus zur Projektion ausgelagert. Es wird also über alle Elemente iteriert, die von Where zurückgegeben wurden, der Wert, den die übergebene Funktion zurück gibt, in die Ausgabemenge eingefügt. Im Beispiel wird von jedem Person-Objekt die Eigenschaft Name zurückgegeben.
Alles in allem wird also der Name von jeder Person über 50 ermittelt. Ist doch eigentlich ganz einfach, oder?


Ausdrucksbaumstrukturen
Wie bereits anfangs erwähnt, bieten Lambda-Ausdrücke die Möglichkeit,  Ausdrucksbaumstrukturen zu erstellen. Das heißt, dass der Methodenrumpf aus dem Lambda-Ausdruck in eine Baumstruktur umgewandelt wird. Dazu muss man einen Lambda-Ausdruck lediglich einem Objekt vom Typ  Expression<TDelegate> (Namensraum  System.Linq.Expressions) zuweisen. Eine wichtige Einschränkung, die man dabei beachten muss, ist, dass man dafür nur Ausdruckslambdas verwenden kann, also Lambdaausdrücke, die Funktionen darstellen. Hier ein Beispiel:

C#-Code (Ausdrucksbaumstruktur für eine Funktion mit einem Parameter):
Expression<Func<int,int>> expression = (x) => 2*x;

Auch in diesem Fall sind Lambda-Ausdrücke wieder nur Compilermagie. Denn "in Wirklichkeit" erweitert der Compiler diese Zeile zu:

C#-Code (Erstellung eine Ausdrucksbaumstruktur ohne Lambdaausdrücke / vom Compiler generierter Code):
ParameterExpression parameterExpression = Expression.Parameter(typeof(int),"x");
ConstantExpression constantExpression = Expression.Constant(2,typeof(int));
Expression body = Expression.Multiply(constantExpression,parameterExpression);
Expression<Func<int,int>> expression = Expression.Lambda<Func<int,int>>(body,new ParameterExpression[] { parameterExpression });

Der Code im Lambda-Ausdruck wird also in Form von Daten dargestellt. Doch wozu das Ganze? Nun, zum einen kann man die Methode  Expression.Compile aufrufen, was einen entsprechenden Delegaten erzeugt. Da man Ausdrucksbäume auch bearbeiten bzw. manuell welche erstellen kann, bietet dies eine Möglichkeit, dynamisch Code zur Laufzeit zu kompilieren. Einen anderen Anwendungszweck veranschaulicht die  IQueryable-Schnittstelle: Diese hat Methoden, welche Expression-Objekte als Parameter annehmen. Anhand des übergebenen Ausdrucksbaums kann beispielsweise der LINQ2SQL-QueryProvider einen äquivalenten SQL-Ausdruck erstellen. Das geht aber nur, weil der Code eben in Form von Daten übergeben wurde.

Den Ausdrucksbaum aus dem obigen Beispiel könnte wieder folgendermaßen "auseinandergenommen" werden:

C#-Code (Auslesen einer Ausdrucksbaumstruktur):
ReadOnlyCollection<ParameterExpression> parameters = expression.Parameters;
BinaryExpression binaryExpression = (BinaryExpression)expression.Body;
ConstantExpression left = (ConstantExpression)binaryExpression.Left;
ParameterExpression parameterExpression = (ParameterExpression)binaryExpression.Right;

Bei Verwendung von Methodenaufrufen, Zugriff auf Eigenschaften, äußere Variablen oder Ähnliches erhält man dann auch Referenzen auf die jeweiligen Metainformationsobjekte (aus dem  System.Reflection-Namensraum).



Zusammenfassung
Delegaten bieten die Möglichkeit, streng Methoden als Argumente an anderen Methoden zu übergeben. Hierfür muss ein typisierter Delegattyp erstellt werden - allerdings erlauben Ko- und Kontravarianz auch ein gewisses Maß an Flexibilität. Durch Kombination ist es auch möglich, Multicastdelegaten zu erstellen. Diese bilden zudem die Grundlage für Ereignisse.

Anonyme Methoden erweitern das Delegatenkonzept und bieten die Möglichkeit, keine separaten Methoden mehr erstellen zu müssen. Durch die Deklaration einer anonymen Methode direkt innerhalb einer anderen Methoden kommt auch das Konzept von so genannten äußeren Variablen ins Spiel.

Mithilfe von Lambda-Ausdrücken kann einerseits die Erstellung von anonymen Methoden noch weiter verkürzt werden. Zudem erhält C# durch Lambda-Ausdrücke funktionale Aspekte und Konzepte wie Currying, Partial Application und Function Composition können leicht umgesetzt werden. Andererseits bieten Lambda-Ausdrücke die Möglichkeit, einfach Ausdrucksbäume erstellen zu können, welche beispielsweise von QueryProvidern (LINQ) benutzt werden.


Schlagwörter: Delegates, anonyme Methoden, Lambda-Ausdrücke, Events, LINQ
Lamda schreibt man mit "b" vor dem "d"!

Dieser Beitrag wurde 11 mal editiert, zum letzten Mal von dN!3L am 18.08.2010 17:53.

26.07.2009 19:19 Beiträge des Benutzers | zu Buddylist hinzufügen
dN!3L dN!3L ist männlich
myCSharp.de-Poweruser/ Experte

images/avatars/avatar-2985.png


Dabei seit: 13.08.2004
Beiträge: 2.886

Themenstarter Thema begonnen von dN!3L

dN!3L ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von dN!3L am 22.02.2010 13:31.

26.07.2009 19:28 Beiträge des Benutzers | zu Buddylist hinzufügen
7.e.Q 7.e.Q ist männlich
myCSharp.de-Mitglied

images/avatars/avatar-3402.jpg


Dabei seit: 06.10.2004
Beiträge: 924
Entwicklungsumgebung: Visual Studio .NET 2010
Herkunft: Scheeßel


7.e.Q ist offline Füge 7.e.Q Deiner Kontaktliste hinzu

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Danke für die Ausführungen. Ich verneige mich vor dir! Daumen hoch Daumen hoch Daumen hoch
26.07.2009 22:49 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
m0rius
myCSharp.de-Mitglied

images/avatars/avatar-3125.png


Dabei seit: 28.08.2007
Beiträge: 1.002
Entwicklungsumgebung: Visual Studio 2013 Ultimate


m0rius ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo dN!3L,

danke für diesen tollen Artikel! Weiter so :)!

m0rius
26.07.2009 23:35 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
andreas-82 andreas-82 ist männlich
myCSharp.de-Mitglied

images/avatars/avatar-2724.gif


Dabei seit: 12.04.2006
Beiträge: 42
Entwicklungsumgebung: Visual Studio 2008 Prof.
Herkunft: Bocholt


andreas-82 ist offline Füge andreas-82 Deiner Kontaktliste hinzu

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo dN!3L,
ich möchte mich auch recht herzlich für diesen wirklich tollen Artikel bedanken Daumen hoch
Hab mich die letzte Stunde mal intensiv deinem Artikel und meinem VS gewidmet.
Echt gut nachvollziehbar und super geschrieben smile

In Sachen Linq, Lambda-Expressions und anonymen delegates ist mir jetzt endlich mal ein Licht aufgegangen smile

Einen schönen Abend noch und weiter so Daumen hoch
25.08.2009 21:35 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Zwischen diesen beiden Beiträgen liegt mehr als ein Monat.
w1z4rd2003 w1z4rd2003 ist männlich
myCSharp.de-Mitglied

images/avatars/avatar-2753.png


Dabei seit: 23.02.2006
Beiträge: 624
Entwicklungsumgebung: VS .NET 2005


w1z4rd2003 ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo dN!3L,

Wirklich ein super Artikel Daumen hoch

Gruss
w1z4rd
12.10.2009 16:59 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Zwischen diesen beiden Beiträgen liegen mehr als 10 Monate.
MrSparkle MrSparkle ist männlich
myCSharp.de-Team

images/avatars/avatar-2159.gif


Dabei seit: 16.05.2006
Beiträge: 4.347
Herkunft: Leipzig


MrSparkle ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Zitat von 7.e.Q:
Danke für die Ausführungen. Ich verneige mich vor dir! :O :O :O

Dem kann ich mich nur anschließen! Was für eine Arbeit du dir gemacht hast für so einen guten Artikel... Vielen Dank!
18.08.2010 16:44 Beiträge des Benutzers | zu Buddylist hinzufügen
Zwischen diesen beiden Beiträgen liegt mehr als ein Monat.
witte
myCSharp.de-Mitglied

Dabei seit: 03.09.2010
Beiträge: 646


witte ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Danke für den ausführlichen Artikel. Ich habe einige Fragen/Bemerkungen:
* Kontravarianz. Das Beispiel (aus dem Zusammenhang gerissen)

C#-Code:
(string name) =>
{
    return "Hello, "+name;
}

wird erläutert mit "Da es ja Kontravarianz gibt und der Compiler zudem den Typen herleiten kann, ...". Kannst Du mal sagen wo da Kontravarianz auftaucht? Der Compiler leitet den Typen ab und fertig.
* Es wäre vorteilhaft gewesen auf die LINQ Query Expressions etwas näher einzugehen. Ich weiß dass der Artikel nicht direkt Linq behandelt und dass ein Link zu diesem Thema eingefügt wurde. Ich denke das es besser für den Leser ist ein solches Beispiel zu sehen. Viele fragen sich 'Weshalb soll ich das verwenden, da spart man vllt etwas Tiparbeit, verschlechtert aber immens die Lesbarkeit des Codes.' Gerade dieser letzte Schritt, die Query Expressions, sind das Ziel gewesen, ein mächtiges Abfragesystem mit intuiver, leicht verständlicher Syntax zu schaffen.
* Auf Expression Trees könnte noch genauer eingegangen werden. Dieses könnte ein mächtiges Werkzeug sein wenn man dem Benutzer gestattet, ausführlich seine zu bearbeitenden Daten filtern zu können, die man dann anfragen muß.
Aber vllt sollte bei der Menge an Informationen dann sowieso ein Teil 2 erscheinen, trotzdem Danke für Deine Bemühungen.

Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von witte am 15.10.2010 13:31.

15.10.2010 13:29 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
dN!3L dN!3L ist männlich
myCSharp.de-Poweruser/ Experte

images/avatars/avatar-2985.png


Dabei seit: 13.08.2004
Beiträge: 2.886

Themenstarter Thema begonnen von dN!3L

dN!3L ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo zusammen,

erstmal vielen Dank für das ganze Feedback. Freut mich, dass der Artikel so gut ankommt.

@witte:

Zitat von witte:
Kannst Du mal sagen wo da Kontravarianz auftaucht? Der Compiler leitet den Typen ab und fertig.

Der Text, den du zitierst, bezieht sich zwar auf einen anderen Codeausschnitt, als von dir gepostet - was aber nebensächlich ist, da du völlig recht hast. Der Compiler leitet einfach die Parametertypen aus dem Typ, an den zugewiesen werden soll, ab und fertig.

Zitat von witte:
Es wäre vorteilhaft gewesen auf die LINQ Query Expressions etwas näher einzugehen. [...] Auf Expression Trees könnte noch genauer eingegangen werden.

In diesem Artikel geht es hauptsächlich - wie der Name ja schon sagt - um Delegaten, anonyme Methoden, Lambda-Ausdrücke & was damit zu tun hat.
Das LINQ-Beispiel soll verdeutlichen, wie man Delegaten einsetzen kann bzw. auch, wie man LINQ-Ausdrücke in Lamda-Form lesen und verstehen kann.
Da man mit Lamda-Ausdrücken auch Ausdrucksbäume erzeugen kann, darf dieser Teil natürlich nicht fehlen. Was man schlussendlich mit den erzeugten Ausdrucksbäumen macht oder wie man Ausdrucksbäume auf andere Art erzeugen kann, ist hier völlig nebensächlich.
Ich versuche hier, den Inhalt des Werkzeugkoffer möglichst detailliert zu erläutern und die Benutzung der Werkzeuge zu erklären. Ich will hier nicht erklären, wie man damit einen Schrank zusammenbaut (falls du damit überhaupt einen Schrank bauen willst).

Besten Gruß,
dN!3L
17.10.2010 14:22 Beiträge des Benutzers | zu Buddylist hinzufügen
Zwischen diesen beiden Beiträgen liegen mehr als 3 Monate.
Stipo Stipo ist männlich
myCSharp.de-Mitglied

images/avatars/avatar-2966.gif


Dabei seit: 09.04.2007
Beiträge: 699
Entwicklungsumgebung: VS 2010 Pro, Blend 4
Herkunft: Lörrach


Stipo ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo dN!3L,

super Artikel, den Du da geschrieben hast Daumen hoch .
Und nachdem ich im letzten halben Jahr soviel wissen dazugelernt habe (hatte den Artikel mitte letzten Jahres schon einmal gelesen, und musste feststellen, das ich deinen ausführungen nicht ganz folgen kann.), kann ich dem Artikel nun auch sehr gut folgen, und konnte noch einige kleine Punkte dazulernen.

Ein riesen Dank von mir, für den super Artikel und die mühe die Du dir damit gegeben hast.

Grüße Stephan
01.02.2011 00:22 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Baumstruktur | Brettstruktur       | Top 
myCSharp.de | Forum Der Startbeitrag ist älter als 7 Jahre.
Der letzte Beitrag ist älter als 5 Jahre.
Antwort erstellen


© Copyright 2003-2016 myCSharp.de-Team. Alle Rechte vorbehalten. 01.10.2016 08:58