Delegaty i wyrażenia Lambda w C#

W tym poście postaram się wytłumaczyć czym są delegaty, anonimowe delegaty (równoważne z wyrażeniem lambda, które jest bardzo przydatne w wielu wypadkach.)

1. Delegaty

Delegaty są podobne do wskaźników i funkcji w C czy C++. Z definicji – delegat to zmienna będąca odniesieniem do innej, przy czym „odniesienie” można zmieniać. Stwórzmy przykładowy delegat:

delegate int NumberOperation(int x, int y);

 

 

Otrzymaliśmy dzięki temu delegat, który zadziała dla każdej operacji, która zwraca typ int oraz ma dwa parametry (w tym przypadku też typu int).

Delegaty, aby można było ich użyć, musimy „utworzyć”, czy też zadeklarować. Robimy to w ten sam sposób co klasy, struktury etc.

NumberOperation adder = new NumberOperation(Add);
NumberOperation subst = new NumberOperation(Substract);
NumberOperation multi = new NumberOperation(Multiply);
NumberOperation divid = new NumberOperation(Divide);

 

 

Gdzie add etc. to nazwa funkcji, którą zadeklarowałem w klasie. Dzięki temu, zamiast wywoływania funkcji możemy wywołać delegat. Kod:

int x = Add(1,2);

 

oraz

int x = adder(1,2);

jest równoważny. Przykładowo taki program:

using System;

namespace DelegatesAndLambda
{

class Program
{

delegate int NumberOperation(int x, int y);

static void Main(string[] args)
{
NumberOperation adder = new NumberOperation(Add);
NumberOperation subst = new NumberOperation(Substract);
NumberOperation multi = new NumberOperation(Multiply);
NumberOperation divid = new NumberOperation(Divide);

int a = 15, b = 5;
Console.Write("Values: {0} {1} {2} {3}", adder(a, b), subst(a, b), multi(a, b), divid(a, b));
Console.ReadKey();
}


static int Substract(int n, int m)
{
return n - m;
}

static int Add(int n, int m)
{
return n + m;
}

static int Multiply(int n, int m)
{
return n * m;
}

static int Divide(int n, int m)
{
return n / m;
}
}
}

da nam takie wyjście:

yuhiiqx

Przejdźmy teraz do ciekawej własności delegatów, to znaczy, da się je do siebie dodawać, odejmować etc. Trzeba zauważyć, że gdy je „dodajemy” etc. zmienia się ich wartość, stara jest usuwana. Przykładowo, dla kodu:

using System;

namespace DelegatesAndLambda
{

class Program
{
delegate string MyDelegate(string s);

static void Main(string[] args)
{
MyDelegate del1;
MyDelegate del2 = new MyDelegate(Reverse);
MyDelegate del3 = new MyDelegate(Duplicate);

del1 = del2;
Console.Write("del1 value: {0} \n", del1("Foo"));
del1 += del3;
Console.Write("del1 value: {0} \n", del1("Bar"));
Console.ReadKey();
}

static string Reverse(string s)
{
string temp = "";
foreach (char c in s) temp = c + temp;
return temp;
}

static string Duplicate(string s)
{
return s + s;
}
}
}

otrzymamy następujące wyjście:aggjkd0

Teraz, gdy wiemy już czym jest delegat, chciałbym omówić inny jego wariant – anonimowy delegat, czyli funkcję bez nazwy. Będzie to świetny wstęp to wyrażenia Lambda.

2. Wyrażenie lambda

Anonimowych delegatów używa się przy eventach, tzn. przy wydarzeniach podczas pracy programu. Dla przykładu, stworzyłem prostą aplikację WPF z jednym przyciskiem, który po naciśnięciu wyświetla okienko z napisem „Hello World!”.

using[...]

namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}

private void Button_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("Hello World!");
}
}
}

kwrklrj

Teraz przejdziemy do użycia delegatów jako metod anonimowych.
Usunę teraz funkcję Button_Click i zastąpię ją anonimowym delegatem – funkcją, która nie ma nazwy! Do zdarzenia button1.Click ‚dodaję’ delegat, zawierający właśnie tę funkcję.

using[...]
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
button1.Click += delegate(object s, RoutedEventArgs e)
{
MessageBox.Show("Hello World!");
};
}
}
}

Jak widać, przy słowie kluczowym delegate mamy, identyczne jak w usuniętej przeze mnie funkcji operatory – object s, RoutedEventArgs e. Są one wymagane przez kompilator, bo domyślnie Button_Click też ich potrzebował.

Z tymi wiadomościami możemy przejść do wyrażenia Lambda.

using [...]
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
button1.Click += (s, e) => MessageBox.Show("Hello World!");
}
}
}

Na pierwszy rzut oka to co tu widzimy może zdawać się niezrozumiałe, jednak gdy się temu przyjrzymy, zaczyna nabierać sensu. Analogicznie jak we wcześniejszym przykładzie do eventu button1.Click dodajemy jakieś wyrażenie. Tylko jakie? (s, e). Czym ono jest? Spójrzmy na parametry w poprzednim przykładzie – (object s, RoutedEventArgs e). Łatwo zauważyć analogię i domyślić się, w jaki sposób to działa. Kompilator wie co z tym zrobić, gdyż zna definicję eventu Click. Później, dzięki => parametry te są przekazywane do wykonywanego kodu. Co w związku z tym? Wyrażenie lambda może być wykorzystywane między innymi do funkcji anonimowych, tzn. delegatów.

Teraz, przejdźmy do wyrażenia lambda w LINQ (Language Integrated Query). Na początku trochę o LINQ – wspomaga bardzo sortowanie, grupowanie i wiele innych operacji, znacząco skraca kod.

using [...]

namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
int[] values = {3, 6, 1, 7, 4, 10, 8, 2};

List<int> biggerThanFive = values.Where(n => n > 5).ToList();
string temp = "";
foreach (int i in biggerThanFive) temp += i.ToString() + " ";
button1.Click += (s, e) => MessageBox.Show(temp);
}
}
}

Co tu się dzieje? Na początku widzimy, została zadeklarowana tablica liczb values z różnymi wartościami. Nastepnie została stworzona lista zawierająca typ int o wiele sugerującej nazwie – biggerThanFive. Do niej zostały przypisane wszyskie wartości z tablicy values, które były większe od pięciu – values.Where( n => n > 5) (Where to element LINQ).

Później widzimy już bardzo standardowy kod – tworzony jest string, do którego przypisywana jest wartość każdego elemntu z listy biggerThanFive a następnie dzieje się to, co już wcześniej omówiliśmy. Delegat wyświetlający nam msgbox z wartością temp. 

To by było na tyle, mam nadzieję, że przekazałem to w całkiem zrozumiały sposób :3

Advertisements

Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Log Out / Zmień )

Zdjęcie z Twittera

Komentujesz korzystając z konta Twitter. Log Out / Zmień )

Facebook photo

Komentujesz korzystając z konta Facebook. Log Out / Zmień )

Google+ photo

Komentujesz korzystając z konta Google+. Log Out / Zmień )

Connecting to %s