XML * XML-SCHEMA * XPATH * XSL * XSL-FO * SVG * XQUERY * XPROC * ANT * DIVERSES



Diverses / C#.NET Grundlagen / C#.NET Lambda Expressions

C#.NET Lambda Expressions

C#.NET Lambda Expressions

➪ Ein Lambda-Ausdruck ist eine Funktion, die einem Lambda-Operator (=>), eine Parameterliste sowie einen Anweisungsblock hat. Lambda-Ausdrücke eignen sich hervorragend für den Einsatz in Delegates.

Auf dieser Seite:

Syntax:


(Parameter) => {Anweisungen}

Delegates und Lambda-Expressions

Sie haben bereits Erfahrungen mit einer alternativen Schreibweise gesammelt haben, zum Beispiel:


public static int Gib76() => (76);

oder


public static int Quadriere(int x) => (x * x);

Deklarieren Sie ein Delegate, dessen Signatur im Rückgabe- und Parametertyp int besteht.


delegate int mydel(int i);

Mit mydel könnten Sie Methoden wie das beschriebene public static int Quadriere(int x) ansprechen.


mydel mdquad = new mydel(Quadriere);
Console.WriteLine(mdquad(9));

Alternativ verwenden Sie es für das Delegate dQuadriere, wo Sie dieselbe Funktionalität per Lambda-Ausdruck codieren: Der übergebene Parameter wird quadriert, ohne eine bestimmte Methode aufzurufen.


static void Main(string[] args)
{
  mydel dQuadriere = x => x * x;
  for(int i=1; i<10; i++)
  {
    int j = dQuadriere(i);
    Console.WriteLine("{0}\t{1}", i, j);
  }
{

Sie können die Weiterverarbeitung des Integer-Parameters x auch in einem Anweisungsblock definieren. Die folgende Logik summiert alle Zahlen von 0 bis 10, das Ergebnis ist 55.


mydel dsummiere = x =>
{
  int sum = 0;
  for (int i = 0; i <= x; i++) sum += i;
  return sum;
};
Console.WriteLine(dsummiere(10));

Ein weiteres Beispiel für die Verwendung von Lambda-Expressions in Verbindung sehen Sie hier. Nehmen Sie die klassisch implementierte Methode IstPositiveZahl, die einen Integer-Parameter übernimmt und einen Boolean-Wert zurückgibt.


static bool IstPositiveZahl(int x)
{
    bool b = false;
    if (x > 0) b = true;
    return b;
}

Der Aufruf im Main sieht so aus:


static void Main(string[] args)
{
    int iwert = 5;
    bool tf = IstPositiveZahl(iwert);
}

Die Alternative wäre, einen Delegate mit entsprechender Signatur zu deklarieren ...


delegate bool dbv(int wert);

... und diesen im Main aufzurufen.


static void Main(string[] args)
{
    int iwert = 5;
    dbv _dbv  = (x) => {
        bool b = false;
        if (x > 0) b = true;
        return b;
    };
    bool tf = _dbv(iwert);
}

Noch kürzer formuliert:


static void Main(string[] args)
{
  int iwert = 5;
  dbv _dbv  = (x) => { return x > 0 ? true : false; };
  bool tf = _dbv(iwert);
}

Während IstPositiveZahl außerhalb von Main implementiert, also quasi ausgelagert ist, arbeitet der Delegate-Lambda-Ansatz im unmittelbaren Kontext der aufrufenden Methode.

Predicate, Func und Action

Was bei dem vorher beschriebenen Ansatz noch stören könnte, ist die Notwendigkeit, delegate bool dbv(int wert); deklarieren zu müssen. Dieses Delegate können Sie sich mit Predicate, Func bzw. Action sparen.


static void Main(string[] args)
{            
    Func<int, bool> mfIstPositiveZahl = (x) =>
    {
        bool b = false;
        if (x > 0) b = true;
        return b;
    };
    bool tf = mfIstPositiveZahl(5);
    Console.WriteLine(tf);
}

Func<int, bool> deklariert hier ein Delegate mit einem int-Parameter und einem bool-Rückgabewert.

Predicate, Func und Action stellen spezielle Delegates dar. Während Func einen Rückgabetyp definiert (der nicht void sein darf), ist das bei Action nicht der Fall. Daher können Sie das Action-Delegate für void-Rückgaben verwenden. Predicate ist ein Delegate, das ausschliesslich bool-Werte zurückgibt.

Statt Code in eine separate Methode auszulagern, können Sie eine lokale Funktion schreiben, die Teil der umgebenden Methode ist. Das macht Sinn, wenn Sie diese Funktionen auch nur lokal verwenden. Sie müssen die Klasse nicht mit Zusatzmethoden und globalen Variablen fluten, die nur lokal in einer einzigen Methode zum Einsatz kommen.


static void Main(string[] args)
{
    Action Striche = () => { 
                   for (int i = 0; i < 45; i++) 
                       Console.Write("="); 
                   Console.WriteLine(); };
    Action<int> Ausgabe_i = x => 
                   Console.WriteLine($"{x,9:N2}");
    Action<decimal> Ausgabe_d = x => 
                   Console.WriteLine($"{x,9:N2}");
    Action<string> Ausgabe_s = x => 
                   Console.WriteLine($"{x,30}");
    Func<int> Gib76 = () => 76;
    Func<int, int> Quadriere_i = (x) => x * x;
    Func<decimal, decimal> Quadriere_d = (x) => x * x;
    Predicate<int> IstGeradeZahl = x => { return (x % 2 == 0); };
    Striche();
    int y = Gib76();
    Ausgabe_i(y);
    Striche();
    y = Quadriere_i(y);
    Ausgabe_i(y);
    Striche();
    Ausgabe_d(Quadriere_d(12.60M));
    Striche();
    if (IstGeradeZahl(y)) Ausgabe_s($"{y} ist eine gerade Zahl");
    Striche();
    Ausgabe_s("Fertig");
    Striche();
}

Das Resultat:


=============================================
    76,00
=============================================
 5.776,00
=============================================
   158,76
=============================================
     5776 ist eine gerade Zahl
=============================================
                        Fertig
=============================================

Damit verbessern Sie das Design und die Wartbarkeit der Klasse ganz erheblich. Sie vermeiden ausgelagerten Code, der möglicherweise anderweit wieder verwendet wird, was bei einer Änderung der Programmteile in eine Code-Hölle führen kann. Die Funktion kann lokal beliebig oft wieder verwendet werden, steht aber für externe Aufrufe nicht zur Verfügung (wo sie schliesslich auch nicht benötigt werden).

Ein Nachteil: Sie können diese lokalen Funktionen nicht überladen. Es ist also nicht möglich, eine Func bzw. eine Action mit demselben Namen, aber einer unterschiedlichen Signatur mehrfach zu implementieren.


Func<int, int>         Quadriere = (x) => x * x;
Func<decimal, decimal> Quadriere = (x) => x * x;
Action<int> Ausgabe = x => 
                   Console.WriteLine($"{x,9:N2}");
Action<decimal> Ausgabe = x => 
                   Console.WriteLine($"{x,9:N2}");

Sie kommen also nicht daran vorbei, für jede Action und Func einen neuen Namen zu vergeben.

Stringformatierung mit Lambda

Sehen Sie sich ein weiteres Beispiel für alternative Implementierungsmöglichkeiten an, etwa im Rahmen der Stringformatierung.

Die folgende Methode übernimmt einen Integer-Parameter und gibt einen individuell konfigurierten String zurück. Hier fungiert {0} als Platzhalter für den ersten Parameter nach dem Komma.


public static string Formatiere(int x)
{
    return String.Format("Der Wert ist: {0:N2}", x);            
}

Dasselbe können Sie alternativ auch so schreiben:


public static string Formatiere(int x) => ($"Der Wert ist: {x:N2}");

Hier steht nicht mehr der Platzhalter {0}, sondern der übergebene Parameter {x:N2} wird direkt angesprochen und formatiert.

In beiden Fällen haben Sie es mit einer "ausgelagerten" Methode zu tun, die Sie in einem anderen Codeteil aufrufen müssen. Kommt die Methode aber nur lokal zum Einsatz, ist es effizienter, sie auch nur lokal zu implementieren.


static void Main(string[] args)
{
    Func<int, string> Formatiere = (x) => ($"Der Wert ist: {x:N2}");
    Console.WriteLine(Formatiere(57));
}

Lambdas und Listen

Siehe auch .

Nehmen Sie eine Func fquadriere als Lambda-Ausdruck, die einen Integerwert als Parameter übernimmt, diesen Wert quadriert und dessen Resultat zurückliefert.


Func<int, int> fquadriere = x => x * x;

Nehmen Sie weiterhin eine Func Zahlen, die zwei Integerparameter start und ende übernimmt und eine generische List<int> zurückgibt; hier wird Func fquadriere aufgerufen.


Func<int, int, List<int>> Zahlen = (start, ende) => {
    List<int> liste = new List<int>();
    for (int i = start; i <= ende; i++)
        liste.Add(fquadriere(i));
    return liste;
};

Nehmen Sie drittens eine Action# ausgabe, die einen Integerparameter übernimmt und diesen via Console.WriteLine formatiert ausgibt.


Action<int> Ausgabe = x => Console.WriteLine($"{x,9:N2}");

Nun lassen Sie eine Schleife über die generierte Liste laufen, wobei jeder Wert Ausgabe übergeben wird.


foreach (var x in Zahlen(1, 10)) Ausgabe(x);

Das Resultat:


     1,00
     4,00
     9,00
    16,00
    25,00
    36,00
    49,00
    64,00
    81,00
   100,00

wg / 4. April 2021



Fragen? Anmerkungen? Tipps?

Bitte nehmen Sie Kontakt zu mir auf.




Arrays



Vielen Dank für Ihr Interesse an meiner Arbeit.



V.i.S.d.P.: Wilfried Grupe * Klus 6 * 37643 Negenborn

☎ 0151. 750 360 61 * eMail: info10@wilfried-grupe.de

www.wilfried-grupe.de/cs_Lambda.html