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:

  1. Func
  2. Action
  3. Predicate
  4. Converter
  5. 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);
        }
    }
}