W dobie wielkiej
popularności wszelakich wtyczek-rozszerzeń-pluginów do programów nie zapomniano również o programistach C#. Od wersji 3.0 języka możemy dodawać w prosty sposób własne metody do już istniejących klas (w praktyce najczęściej - typów). Wydaje się, że to nic nowego - od dawna przecież mamy do dyspozycji np. rozszerzanie klas przez dziedziczenie. Nowe podejście - tzw. metody rozszerzające mają jednak pewne zalety, dla których (moim zdaniem) warto je poznać.
Metody rozszerzające poznałem podczas zabaw z projektem, który dotyczył obliczania różnych własności ciągów liczbowych. Testowałem, czy dana liczba spełnia jakąś właściwość matematyczną oraz obliczałem różne wielkości dla całego ciągu liczb (umieszczonych w tablicy).
W kodzie często pojawiały się konstrukcje typu
wynik=funkcja1(funkcja2(tablica), argument)
wynik=tablica.funkcja2(argument).funkcja1()
Problem tylko w tym, że nie miałem ochoty rozszerzać typu int tworząc nieco na siłe jakąś nową klasę np. Calkowite z potrzebnymi metodami: funkcja1 oraz funkcja2. Poza tym może się okazać, że klasa, z której chcemy dziedziczyć zabrania tego - tzn. jest oznaczona modyfikatorem 'sealed' (tak właśnie jest przypadku typu int - przy próbie dziedziczenia po nim kompilator zgłasza błąd: "...cannot derive from sealed type 'int'... ").
Jak to zwykle bywa w wielu przypadkach - google znał rozwiązanie mojego problemu: w pierwszych linijkach zwrócił hasło 'extension methods'.
Okazuje się, że jest to elegancki i prosty sposób na niejako dodanie własnych metod do istniejących już klas (typów).
W dodatku nie potrzebujemy nawet dostępu do źródeł tych klas i w związku z tym nie ma potrzeby przekompilowywania jej kodu.
Najlepiej wyjaśni to przykład:
Chcemy mieć możliwość wywołania na każdej zmiennej typu int metody
czyParzysta()
public static class MetodyRozszerzajace { public static bool czyParzysta(this int liczba) { return liczba%2==0; } }
Od tego momentu można już używać w kodzie wywołania
x.czyParzysta()
Nowa metoda jest również widoczna w podpowiedziach IntelliSense w Visual Studio.
Pierwszym argumentem takiej metody musi być zmienna typu, który chcemy rozszerzyć poprzedzona słowem this:
public static zwracany_typ nazwa_metody(this rozszerzany_typ agument, pozostałe_argumenty...)
Poniżej prezentuję kod prościutkiej aplikacji konsolowej, która pokazuje użycie takich metod,a jej działanie ogranicza się do wyszukiwania w wypełnionej losowo tablicy liczb całkowitych tych, które są jednocześnie elementami ciągu Fibonacciego.
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace MetodyRozszerzajace1 { static class MetodyRozszerzajace { //sprawdza, czy argument jest liczbą parzystą public static bool czyParzysta(this int liczba) { return liczba % 2 == 0; } //znajduje n-tą liczbę Fibonacciego rekurencyjnie //to nie jest metoda rozszerzająca private static int liczbaFibonacciego(int n) { if (n == 0 || n == 1) return n; else return liczbaFibonacciego(n - 1) + liczbaFibonacciego(n - 2); } //testuje, czy dana liczba występuje w ciągu Fibonacciego //na podstawie: http://en.wikipedia.org/wiki/Fibonacci_number#Recognizing_Fibonacci_numbers public static bool czyLiczbaFibonacciego(this int liczba) { double phi=(1+Math.Sqrt(5))/2.0; return MetodyRozszerzajace.liczbaFibonacciego((int)Math.Floor(Math.Log(liczba * Math.Sqrt(5), phi) + 0.5)) == liczba; } //zbior metod rozszerzających wypisujących na konsolę wartości argumentow public static void wyswietl(this int argument,String dodatkowyTekst) { Console.Write(argument.ToString()+dodatkowyTekst); } public static void wyswietl(this double argument) { Console.Write(argument.ToString()); } public static void wyswietl(this bool argument) { Console.Write(argument.ToString()); } public static void wyswietl(this string argument) { Console.WriteLine(argument); } //wyświetla tablicę liczb w 'przyjaznej' postaci public static void wyswietl(this int[] tablica) { string s = "["; foreach (int liczba in tablica) s += liczba.ToString() + ","; s=s.Remove(s.LastIndexOf(',')); s += "]"; Console.WriteLine(s); } } class Program { static void Main(string[] args) { int n = 40; //ilosc losowanych liczb int min = 0; int max = 100; int[] losowe = new int[n]; Random rnd = new Random(System.DateTime.Now.Millisecond); for (int i = 0; i < n; i++) losowe[i] = rnd.Next(min, max); Console.WriteLine("Wylosowałem {0} liczb z zakresu {1} - {2} :\r\n", n, min, max); losowe.wyswietl(); //użycie metody rozszerzającej wyswietl() na tablicy liczb całkowitych "\r\n".wyswietl(); //odstęp "W powyższym ciągu następujące liczby są elementami ciągu Fibonacciego\r\n:".wyswietl(); //wyświetlamy teraz tylko te, które są elementami ciągu Fibonacciego foreach(int liczba in losowe) { if (liczba.czyLiczbaFibonacciego()) //kolejne rozszerzenie w akcji { liczba.wyswietl(" "); } } "\r\n".wyswietl(); "Te same dane zwraca zapytanie LINQ:\r\n".wyswietl(); //to samo, ale za pomocą zapytania LINQ var liczbyFibonacciego = from liczba in losowe where liczba.czyLiczbaFibonacciego() select liczba; foreach (var liczba in liczbyFibonacciego) liczba.wyswietl(" "); Console.ReadKey(); } } }
|
|