Delegaci i wyrażenia lambda

Delegat definiuje typ reprezentujący odwołania do metod, które mają określoną listę parametrów i typ zwracany. Metoda (statyczna lub wystąpienie), której lista parametrów i zwracane dopasowanie typu można przypisać do zmiennej tego typu, a następnie wywołać bezpośrednio (z odpowiednimi argumentami) lub przekazać jako argument sam do innej metody, a następnie wywołać. W poniższym przykładzie pokazano użycie delegata.

using System;
using System.Linq;

public class Program
{
    public delegate string Reverse(string s);

    static string ReverseString(string s)
    {
        return new string(s.Reverse().ToArray());
    }

    static void Main(string[] args)
    {
        Reverse rev = ReverseString;

        Console.WriteLine(rev("a string"));
    }
}
  • Wiersz public delegate string Reverse(string s); tworzy typ delegata metody, która przyjmuje parametr ciągu, a następnie zwraca parametr ciągu.
  • Metoda static string ReverseString(string s) , która ma dokładnie tę samą listę parametrów i zwraca typ co zdefiniowany typ delegata, implementuje delegata.
  • Wiersz Reverse rev = ReverseString; pokazuje, że można przypisać metodę do zmiennej odpowiadającego typu delegata.
  • Wiersz Console.WriteLine(rev("a string")); pokazuje, jak używać zmiennej typu delegata do wywoływania delegata.

Aby usprawnić proces programowania, platforma .NET zawiera zestaw typów delegatów, których programiści mogą używać ponownie i nie muszą tworzyć nowych typów. Te typy to Func<>, Action<> i Predicate<>, i mogą być używane bez konieczności definiowania nowych typów delegatów. Istnieją pewne różnice między trzema typami, które muszą mieć związek ze sposobem ich użycia:

  • Action<> jest używany, gdy istnieje potrzeba wykonania akcji przy użyciu argumentów delegata. Metoda, która hermetyzuje, nie zwraca wartości.
  • Func<> jest zwykle używany, gdy masz transformację pod ręką, oznacza to, że należy przekształcić argumenty delegata w inny wynik. Projekcje są dobrym przykładem. Metoda, która hermetyzuje, zwraca określoną wartość.
  • Predicate<> jest używany, gdy trzeba określić, czy argument spełnia warunek delegata. Można go również zapisać jako Func<T, bool>wartość , co oznacza, że metoda zwraca wartość logiczną.

Teraz możemy użyć naszego przykładu powyżej i przepisać go przy użyciu delegata Func<> zamiast typu niestandardowego. Program będzie nadal działać dokładnie tak samo.

using System;
using System.Linq;

public class Program
{
    static string ReverseString(string s)
    {
        return new string(s.Reverse().ToArray());
    }

    static void Main(string[] args)
    {
        Func<string, string> rev = ReverseString;

        Console.WriteLine(rev("a string"));
    }
}

W tym prostym przykładzie posiadanie metody zdefiniowanej Main poza metodą wydaje się nieco zbędne. Program .NET Framework 2.0 wprowadził koncepcję anonimowych delegatów, co umożliwia tworzenie delegatów "wbudowanych" bez konieczności określania dodatkowego typu lub metody.

W poniższym przykładzie anonimowy delegat filtruje listę tylko do liczb parzysowych, a następnie wyświetla je w konsoli.

using System;
using System.Collections.Generic;

public class Program
{
    public static void Main(string[] args)
    {
        List<int> list = new List<int>();

        for (int i = 1; i <= 100; i++)
        {
            list.Add(i);
        }

        List<int> result = list.FindAll(
          delegate (int no)
          {
              return (no % 2 == 0);
          }
        );

        foreach (var item in result)
        {
            Console.WriteLine(item);
        }
    }
}

Jak widać, treść delegata jest tylko zestawem wyrażeń, podobnie jak każdy inny delegat. Ale zamiast być oddzielną definicją, wprowadziliśmy ją ad hoc w wywołaniu List<T>.FindAll metody .

Jednak nawet w przypadku tego podejścia nadal istnieje wiele kodu, który możemy wyrzucić. W tym miejscu pojawiają się wyrażenia lambda. Wyrażenia lambda lub tylko "lambda" na krótko, zostały wprowadzone w języku C# 3.0 jako jeden z podstawowych bloków konstrukcyjnych zapytania zintegrowanego języka (LINQ). Są to po prostu wygodniejsza składnia używania delegatów. Deklarują listę parametrów i treść metody, ale nie mają własnej formalnej tożsamości, chyba że są przypisane do delegata. W przeciwieństwie do delegatów, można je przypisać bezpośrednio jako prawą stronę rejestracji zdarzeń lub w różnych klauzulach i metodach LINQ.

Ponieważ wyrażenie lambda jest po prostu innym sposobem określania delegata, powinniśmy mieć możliwość ponownego zapisania powyższego przykładu, aby użyć wyrażenia lambda zamiast anonimowego delegata.

using System;
using System.Collections.Generic;

public class Program
{
    public static void Main(string[] args)
    {
        List<int> list = new List<int>();

        for (int i = 1; i <= 100; i++)
        {
            list.Add(i);
        }

        List<int> result = list.FindAll(i => i % 2 == 0);

        foreach (var item in result)
        {
            Console.WriteLine(item);
        }
    }
}

W poprzednim przykładzie używane wyrażenie lambda to i => i % 2 == 0. Ponownie jest to po prostu wygodna składnia używania delegatów. To, co dzieje się w ramach okładek, jest podobne do tego, co dzieje się z pełnomocnikiem anonimowym.

Ponownie lambdy są tylko delegatami, co oznacza, że mogą być używane jako program obsługi zdarzeń bez żadnych problemów, jak pokazano w poniższym fragmencie kodu.

public MainWindow()
{
    InitializeComponent();

    Loaded += (o, e) =>
    {
        this.Title = "Loaded";
    };
}

Operator += w tym kontekście służy do subskrybowania zdarzenia. Aby uzyskać więcej informacji, zobacz Jak subskrybować i anulować subskrypcję zdarzeń.

Dalsze informacje i zasoby