Artykuł

lut 07 2013
0

Metody rozszerzeń w C#

Jedną z ogromnych zalet programowania obiektowego jest to, że raz napisany kod możemy łatwo rozszerzyć o dowolne konstrukcje, lub stworzyć zupełnie coś nowego na jego podstawie. W tym przypadku chodzi mi głównie o kwestię dziedziczenia, która ma swoje wady i zalety.

W tym przypadku bardziej chciałbym skupić się na tych pierwszych. Dziedziczenie nie zawsze jest do końca dobrym rozwiązaniem, ponieważ w C# możemy rozszerzać tylko jedną klasę (ale za to na szczęście możemy implementować dowolną ilość interfejsów). Ponadto dziedziczenie nie zawsze jest do końca efektywne, w szczególności jeśli dodawana funkcjonalność jest mała - w takim przypadku ilość pobocznego kodu potrzebnego do wykonania tego zadania może być większa niż właściwa treść.

W takim oraz kilku innych przypadkach z pomocą mogą przyjść metody rozszerzające - temat dzisiejszego wpisu.

Istota metod rozszerzających

Metody rozszerzające to bardzo ciekawa konstrukcja, która umożliwia szybkie dodanie funkcjonalności do już istniejących klas, tworząc tym samym ciekawą alternatywę dla dziedziczenia. Funkcjonalność ta została w 3 wersji frameworka .Net (3.0) i bardzo często jest ona wykorzystywana do tworzenia nowych metod dla klasy string. Dzięki takiemu zabiegowi, możemy łatwo utworzyć nowe konstrukcje, które będą widoczne w obszarze naszego zestawu. Możliwe jest również umieszczenie kodu rozszerzeń w bibliotekach, które podpinamy do różnych naszych projektów.

Metody rozszerzeń muszą być statyczne i muszą korzystać ze słowa kluczowego this w definicji parametru. To właśnie dzięki takiemu parametrowi kompilator wie, że ma do czynienia z metodą rozszerzającą istniejąca klasę.

Metody rozszerzeń mają niestety swoje ograniczenia. Operować możemy w tym przypadku tylko na publicznie dostępnych właściwościach oraz metodach. Odpadają nam tym samym np. wszystkie elementy typu protected. W istocie w tym przypadku operujemy na zamkniętym organizmie, a metody rozszerzeń to tylko taka doklejka do pewnej spójnej całości, czym budzi różne kontrowersje wśród niektórych programistów.

Przykład praktyczny

Tak jak wspominałem wcześniej, sztandarowym przykładem metod rozszerzeń jest klasa string, dlatego również się nią posłużę. Napiszemy dwie proste metody. Pierwsza z nich, wyświetli przekazany łańcuch z kapitalikami (duże pierwsze litery dla każdego z wyrazów składowych), a druga zwróci podaną liczbę słów ze wskazanego tekstu.

Pracę zaczniemy od zdefiniowania klasy przeznaczonej na przechowywanie statycznych metod rozszerzeń:

public static class StaticExtensions
{
    public static string Capitalize(this string input)
    {
        throw new NotImplementedException();
    }

    public static string Words(this string input, int numberOfWords)
    {
        throw new NotImplementedException();
    }
}

W przypadku tego listingu warto zwrócić oczywiście na słowo kluczowe static umieszczone w deklaracji klasy oraz metod, a także na wspominane wcześniej słowo kluczowe this, które tak naprawdę określa to jaki typ rozszerza określona metoda.

Parametr wykorzystujący to słowo kluczowe jest bezwzględnie wymagany, inaczej po prostu uzyskamy zwykłą metodę i nasze rozszerzenia nie będą działać dla łańcuchów. Do każdej z metod możemy również przekazać dowolne inne parametry (będą one już normalnie wykorzystane w wywołaniu metody).

Musimy również pamiętać o tym, że typ zwracany nie musi być zgodny z typem parametru stojącego przy this - oczywiście jeśli dana metoda ma coś zwracać (w przypadku klasy string jest to raczej konieczne, ponieważ taka jest istota większości metod tej klasy - zwracanie przetworzonego łańcucha).

To tyle z nowości. Implementacja poszczególnych metod nie powinna nastręczać większych problemów. Na początek metoda Capitalize:

public static string Capitalize(this string input)
{
    if(string.IsNullOrEmpty(input))
    {
        return input;
    }
    input = input.ToLower();
    string[] parts = input.Split(' ');
    Func<string, string> capitalizer = (s) => ((s.Length > 1) ?
        s.Substring(0, 1).ToUpper() + s.Substring(1) :
        s.ToUpper());
    return parts.Aggregate((current, next) => 
        capitalizer(current) + " " + capitalizer(next));
}

Na wstępie sprawdzamy czy łańcuch nie jest pusty. Jeśli nie, to stosujemy metodę ToLower by wymusić małe litery dla całego tekstu. Następnie dzielimy łańcuch na części oraz tworzymy funktor, który umożliwi podniesienie pierwszej litery każdego przekazanego słowa. Sprawdzamy w tym przypadku, czy każde słowo posiada więcej niż jeden znak. W pozostałych przypadkach wystarczy podnieść całe słowo.

W konstrukcji return wykorzystujemy metodę Aggregate do sprawnego połączenia i podniesienia wszystkich wyrazów.

W drugiej z naszych metod, zwrócimy łańcuch, który będzie zawierać tylko wskazaną przez nas liczbę słów. Znów wykorzystamy w tym celu metody LINQ (czytaj więcej na temat LINQ):

public static string Words(this string input, int numberOfWords)
{
    if (string.IsNullOrEmpty(input))
    {
        return input;
    }
    string[] parts = input.Split(' ');
    return parts.Take(numberOfWords).Aggregate(
        (current, next) => current + " " + next);
}

Metoda Take pobiera zadaną liczbę elementów z kolekcji, a poznana już wcześniej metoda Aggregate łączy je według zadanego wzorca. Tym razem wykorzystaliśmy również przekazany przez użytkownika parametr numberOfWords.

Cały powyższy kod, można bardzo łatwo przetestować:

string testString = "to jest tekst który zostanie napisany z kapitałkami";
Console.WriteLine(testString.Capitalize());
// To Jest Tekst Który Zostanie Napisany Z Kapitałkami
Console.WriteLine(testString.Words(3));
// to jest tekst

W przypadku metod rozszerzeń klasy string, warto pomyśleć o obsłudze ustawień regionalnych (klasa CultureInfo).

Data ostatniej modyfikacji: 07.05.2013, 11:51.

Podoba Ci się ten wpis? Powiedz o tym innym!

Send to Kindle

Komentarze

blog comments powered by Disqus