Paweł Łukasiewicz
2015-08-14
Paweł Łukasiewicz
2015-08-14
Delegaty są bardzo potężnym narzędziem dostępnym w środowisku .NET. W tym artykule przypomnę po krótce zastosowanie
delegatów oraz opiszę ich nowoczesne funkcjonalności.
Omówione zostaną poniższe funkcjonalności:
- Func
- Action
- Predicate
- Converter
- Comparison
Czym są delegaty?
W ramach przypomnienia sobie informacji o delegatach odsyłam do artykułu zamieszczonego na tej stronie ->
"C# - delegaty".
Poniższy opis będzie nieco mniej szczegółowy. W najprostszych słowach, delegat to wskaźnik do metody. Delegat może być przekazany jako parametr
do metody. Dzięki temu możemy w trakcie wykonywania programu zmienić implementację metody, należy jedynie pamiętać o poprawności parametrów
przekazywanych i zwracanach.
Przykład: jeżeli zadeklarujemy delagata z typem zwracanym int do którego przekazujemy dwa parametry,
int oraz string należy pamiętać aby wszystkie referencje używające tego
delagata miały identyczną sygnaturę.
using System;
namespace Delegate
{
class Program
{
protected delegate int MyDelegate(string stringParameter, int intParameter);
static void Main(string[] args)
{
// tworzymy instancję naszej w klasie w której zdefiniowane zostały metody
DelegateSample ds = new DelegateSample();
// tworzony nowy obiekt delegata przy użyciu pierwszej metody
MyDelegate myOwnDelegate = new MyDelegate(ds.FirstMethod);
// wywołanie metody przy użyciu delegata
myOwnDelegate("Hello", 1);
// tworzymy obiekt delegata przy użyciu drugiej metody
myOwnDelegate = new MyDelegate(ds.SecondMethod);
// wywołanie metody przy użyciu delegata
myOwnDelegate("Hello again", 2);
Console.ReadKey();
// Wynik działania programu
// Wnetrze pierwszej metody
// Hello
// Wnetrze drugiej metody
// Hello again
}
}
class Empolyee
{
public string Name { get; set; }
public int Age { get; set; }
}
class ExEmployee : Empolyee
{
public bool isExEmployee { get; set; }
}
class DelegateSample
{
public int FirstMethod(string strParameter, int intParameter)
{
Console.WriteLine("Wnętrze pierwszej metody");
Console.WriteLine(strParameter);
return intParameter;
}
public int SecondMethod(string strParameter, int intParameter)
{
Console.WriteLine("Wnętrze drugiej metody");
Console.WriteLine(strParameter);
return intParameter;
}
}
}
Delegaty mogą być wykonywane synchronicznie lub asynchronicznie. Powyższy kod jest przykładem wywołania synchronicznego. Aby kod ten uczynić
asynchronicznym należy użyć metody BeginInvoke:
myOwnDelegate.BeginInvoke("Hello", 1, null, null);
Pierwsze dwa parametry to dane wejściowe dla metody. Trzeci parametr może być ustawiony w celu odebrania wiadomości zwrotnej po wykonaniu
procesu (call back). Szczegółowe wyjaśnienie wywołania asynchronicznego znajduje się w
artykule -> https://msdn.microsoft.com/en-us/library/2e08f6yc%28v=vs.110%29.aspx
Kiedy używać delegatów?
Patrząc na powyższy przykład wielu z Was może powiedzieć, że podobny rezultant może być osiągnięty za pomocą interfejsów lub klas abstrakcyjnych.
Po co więc używać delegata?
Delegaty mogą być używane w poniższych scenariuszach:
- jeżeli nie chcesz przekazywać interfejsu lub klasy abstrakcyjnej do innej warstwy aplikacji bądź klasy;
-
jeżeli kod nie potrzebuje dostępu do żadnych innych atrybutów czy pól a jedynie do metod klasy z której potrzebna jest logika do
przetworzenia danych;
- należy dokonać implementacji obsługi zdarzeń (Event).
Func<TParametr, TWyjście>
Func jest logicznie podobny do bazowej implementacji delegata. Różnica jest w deklaracji. W momencie deklaracji
musimy zapewnić sygnaturę parametrów oraz typ zwracany.
Func<string, int, int> funcDeclaration;
Pierwsze dwa parametry to parametry wejściowe metody. Trzeci parametr (zawsze ostatni) jest parametrem wyjściowym, który powinien być parametrem
zwracanym przez metodę.
using System;
namespace Func
{
class Program
{
static void Main(string[] args)
{
FuncSample fs = new FuncSample();
// Tworzymy delegata Func, który pozwala na zwrócenie typu lub obiektu z metody
Func<string, string, double> myFuncDeclaration = fs.ReturnPrice;
// wywołanie metody przy użyciu delegata
double price = myFuncDeclaration("Audi", "RS6");
Console.WriteLine("Cena Audi RS6: {0}zl", price);
Console.ReadKey();
// Wynik działania programu
// Cena Audi RS6: 560000zl
}
}
class Car
{
public string Brand { get; set; }
public string Model { get; set; }
}
class FuncSample
{
public double ReturnPrice(string brand, string model)
{
if (brand.Equals("Audi") && model.Equals("RS6"))
return 560000;
if (brand.Equals("Audi") && model.Equals("R8"))
return 860000;
else
return -1;
}
}
}
Func jest zawsze używany, gdy musimy zwrócić obiekt lub typ z metody. Jeżeli nasza metoda nie zwraca nic
(void) należy użyć Action.
Action<TParametr>
Action jest używany, gdy nasza nie zwraca żadnego typu. Metoda z sygnaturą void
jest używana z delagatem Action.
Action<string, int> myActionDeclaration;
Podobnie do delegata Func, pierwsze dwa parametry to parametry przekazywane do metody. Skoro nie mamy żadnego
zwracanego typu ani obiektu, wszystkie parametry traktowane są jako parametry wejściowe.
using System;
namespace Action
{
class Program
{
static void Main(string[] args)
{
FuncSample fc = new FuncSample();
// Tworzymy delegata Action - nie zwracamy typu lub obiektu z naszej metody
Action<string, string> myActionDeclaration = fc.DisplayData;
// wywołanie metody przy użyciu delegata
myActionDeclaration("Audi", "R8");
Console.ReadKey();
// Wynik działania programu
// Wybrales auto sportowe: Audi R8
}
}
class Car
{
public string Brand { get; set; }
public string Model { get; set; }
}
class FuncSample
{
public void DisplayData(string brand, string model)
{
if (brand.Equals("Audi") && model.Equals("RS6"))
Console.WriteLine("Wybrałeś auto 'rodzinne': {0} {1}", brand, model);
if (brand.Equals("Audi") && model.Equals("R8"))
Console.WriteLine("Wybrałeś auto sportowe: {0} {1}", brand, model);
else
Console.WriteLine("Nieznany rodzaj samochodu");
}
}
}
Predicate<TWejście>
Predicate jest funkcyjnym wskaźnikiem do metody, która zwraca wartość logiczną. Jest on przeważnie używany do
interacji po kolekcjach lub do weryfikacji czy dana wartość istnieje.
Predicate<Car> myPredicateDeclaration;
Dla przykładu utworzony tablice przechowującą listę samochodów. Predicate będzie używany do zwrócenia samochodu
tańszego niż 10 000zł.
using System;
namespace Predicate
{
class Program
{
static void Main(string[] args)
{
Car car = new Car();
// Wypełniamy tablicę elementami
Car[] carList = new Car[]
{
new Car("Audi", "RS6", 560000),
new Car("Audi", "R8", 860000),
new Car("Audi", "100", 6600),
new Car("Audi", "RS4 B5", 55000)
};
// Tworzymy naz predykat, który zwróci z naszej listy samochód tańszy niż 10 000zł
Predicate<Car> myPredicateDef = car.ReturnCheapCar;
Car tempCar = Array.Find(carList, myPredicateDef);
Console.WriteLine("Samochód o cenie poniżej 10 000zł: {0} {1}", tempCar.Brand, tempCar.Model);
Console.ReadKey();
// Wynik działania programu
// Samochód o cenie ponizej 10 000zl: Audi 100
}
}
class Car
{
public string Brand { get; set; }
public string Model { get; set; }
public double Price { get; set; }
public Car(string brand, string model, double price)
{
this.Brand = brand;
this.Model = model;
this.Price = price;
}
public Car()
{
}
public bool ReturnCheapCar(Car car)
{
return car.Price < 100000;
}
}
}
Converter<TWejście, TWyjście>
Converter jest używany, gdy jedną kolekcję chcemy przekształcić na drugą używając do tego algorytmu. Obiekt
A zostaje przekonwertowany na obiekt B.
Converter<Car, UsedCar> myConverterDefinition = new Converter<Car, UsedCar>(uc.ReturnUsedCar);
Dla przykładu utworzymy tablicę list używanych. Tablica ta powstanie z przekonwertowania obiektu Car na
UsedCar.
using System;
namespace Converter
{
class Program
{
static void Main(string[] args)
{
UsedCar uc = new UsedCar();
// Wypełniamy tablicę elementami
Car[] carList = new Car[]
{
new Car() {Brand = "Audi", Model = "100", Year=1992 },
new Car() {Brand = "Audi", Model = "RS6 C6", Year=2007 },
new Car() {Brand = "Audi", Model = "R8", Year=2010 }
};
// tworzymy delegat, który konwertuje jeden obiekt na drugi
Converter<Car, UsedCar> myConvertDefinition = new Converter<Car, UsedCar>(uc.ReturnUsedCar);
// dokonujemy konwersji naszej tablicy carList na tablicę samochodów używanych
Car[] tempCar = Array.ConvertAll(carList, myConvertDefinition);
foreach (UsedCar usedCar in tempCar)
{
Console.WriteLine("Używany samochód: {0} {1} rok: {2}", usedCar.Brand, usedCar.Model, usedCar.Year);
}
Console.ReadKey();
// Wynik działania programu
// Uzywany samochód: Audi 100 rok: 1992
// Uzywany samochód: Audi RS6 C6 rok: 2007
// Uzywany samochód: Audi R8 rok: 2010
}
}
class Car
{
public string Brand { get; set; }
public string Model { get; set; }
public int Year { get; set; }
}
class UsedCar : Car
{
public bool IsOldCar { get; set; }
public UsedCar ReturnUsedCar(Car car)
{
return new UsedCar() { Model = car.Model, Brand = car.Brand, Year = car.Year };
}
}
}
Comparison<T>
Comparison jest używany aby uporządkować lub posortować dane wewnątrz kolekcji. Delegat przyjmuje jako parametr
typ generyczny a typ zwracany zawsze musi być liczbą całkowitą, tj. int. Poniżej przykładowa deklaracja delagata:
Comparison<Car> myComparisonDefinition = new Comparison<Car>(uc.CompareNamesInCollection);
W powyższym przykładzie tworzymy delegat do porównania dwóch obiektów tego samego typu. Zdanie to stanie się jaśniejsze po spojrzeniu w poniższy
przykład.
using System;
namespace Comparison
{
class Program
{
static void Main(string[] args)
{
Car uc = new Car();
// Wypełniamy tablicę elementami
Car[] carList = new Car[]
{
new Car() {Brand = "Audi", Model = "RS6 C6", Year=2007 },
new Car() {Brand = "Audi", Model = "R8", Year=2010 },
new Car() {Brand = "Audi", Model = "100", Year=1992 },
new Car() {Brand = "BMW", Model = "M3", Year=1992 }
};
// Tworzymy delegat do porównania dwóch obiektów tego samego typu
Comparison<Car> myComparisonDefinition = new Comparison<Car>(uc.CompareNamesInCollection);
// W trakcie sortowania tablicy będziemy korzystać z przygotowanego wcześniej delegata
// Sortowanie tablicy będzie się odbywało po modelu samochodu a nie jego marce
Array.Sort(carList, myComparisonDefinition);
Console.WriteLine("Lista posorotwana po modelu: ");
foreach (Car item in carList)
{
Console.WriteLine("{0} {1}", item.Brand, item.Model);
}
Console.ReadKey();
// Wynik działania programu
// Lista posorotwana po modelu:
// Audi 100
// BMW M3
// Audi R8
// Audi RS6 C6
}
}
class Car
{
public string Brand { get; set; }
public string Model { get; set; }
public int Year { get; set; }
public int CompareNamesInCollection(Car firstParameter, Car secondParameter)
{
return firstParameter.Model.CompareTo(secondParameter.Model);
}
}
}