Leniwce.com | blog technologiczny
Metody rozszerzające
Kuba Krakowski, 2010-05-07 00:00:00
kategoria: C#

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)
i w pewnym momencie pomyślałem, że dobrze byłoby pisać w stylu bardziej obiektowym:
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()
sprawdzającej, czy argument jest liczbą parzystą. Aby to było możliwe trzeba żądaną metodę umieścić w osobnej statycznej klasie np:

 
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()
dla każdej zmiennej x typu int.

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...)

Kluczowe jest słowo this - informuje ono, że metoda zostanie dodana do klasy wynienionej po nim - w naszym przypadku int.

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();       
        }
    }
}
 
 
 
Powiązane artykuły
Atraktor Lorenza (2011-08-29)
Równoległy może więcej(?) - czyli kilka słów o Parallel.For (2011-02-06)
Święta, święta - czas pochwalić się (fraktalną) choinką (2010-12-25)
Własny wygaszacz ekranu (2010-06-03)
3.1415926535897932385... (2010-05-19)
GPS w lekkostrawnym sosie podany (2009-08-05)
Wszechświat na ekranie, czyli słowo o skalowaniu (2009-07-15)
OleDbConnection – Excel jako baza danych(C#) (2009-07-13)
Animacje 3D (OpenGL) (2009-07-05)
Separator dziesiętny w C# (2009-06-29)


Komentarze


xxx [2010-11-10 20:37:00]
dzieki. przypalo mi sie przy projekcie. Rozszerzylem sobie stringa by dodac do niego cos w php-owym addslasges. I teraz mam:
string s;
s=s.addSlashes();

great!

Dodaj komentarz:
Autor:*

WWW:

Treść:*

Wprowadź kod zabezpieczający*:


        * - pola wymagane
Kategorie
C# (13)
Inne (6)
Java (3)
Matlab (1)
OpenGL (1)
PHP (2)


Najnowsze wpisy

Ostatnie komentarze