Udostępnij za pośrednictwem


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. Metodę (statyczną lub instancję), gdzie lista parametrów i typ zwracany pasują, można przypisać do zmiennej tego samego typu. Następnie można ją wywołać bezpośrednio (z odpowiednimi argumentami) lub przekazać jako argument do innej metody, a potem wywołać. W poniższym przykładzie zademonstrowano 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 ten sam typ zwracany jak 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 nie zwraca wartości, którą enkapsuluje.
  • 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órą hermetyzuje, zwraca określoną wartość.
  • Predicate<> jest używany, gdy trzeba określić, czy argument spełnia warunek delegata. Można to również zapisać jako Func<T, bool>, co oznacza, że metoda zwraca wartość boolowską.

Teraz możemy skorzystać z naszego powyższego przykładu i przepisać go, używając 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 definiowanie metody poza stosowaną 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ć, ciało 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, zwane także krócej "lambda", zostały wprowadzone w języku C# 3.0 jako jeden z kluczowych elementów wbudowanych w zapytania zintegrowane z językiem (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ę za kulisami, jest podobne do tego, co dzieje się z delegatem 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 zrezygnować z subskrypcji zdarzeń.

Dalsze informacje i zasoby