Metody rozszerzające w C#

2009-09-11 12:28

Wspomnę dzisiaj o dość dziwnej funkcji, która została dodana w wersji 3.0 języka C#. Polega ona na możliwości dodania nowych metod do istniejących klas bez zmiany ich definicji. Odbywa się to poprzez zdefiniowanie tych dodatkowych metod jako statycznych (w innych klasach) i użyciu specjalnej składni dla jej pierwszego parametru. Oto przykład:

  1. public static class StringExtensions
  2. {
  3.     // zwraca liczbę znaków w ciągu
  4.     public static int WordCount(this String str)
  5.         { return str.Split(new char[] { ' ' }).Length; }
  6. }

Jeśli ktoś co nieco wie o tym, jak od środka działają metody obiektów w C++, to pewnie przypomina sobie, że to co wewnątrz metody jest wskaźnikiem this w rzeczywistości przekazywane jest metodzie jako jej pierwszy parametr. Pewnie ten fakt posłużył twórcom C# jako inspiracja przy ustalaniu składni metod rozszerzających.
Jak to działa? Otóż bardzo prosto. Dzięki powyższej definicji standardowa klasa System.String została teraz wzbogacona o nową metodę WordCount, której możemy użyć tak samo, jak każdej innej metody tej klasy:

  1. string s = "Ala ma kota";
  2. int words = s.WordCount();

Powód, dla którego fakt istnienia podobnego feature‘a nazwałem dość dziwnym, powinien się w tej chwili stać nieco jaśniejszy. Po krótkim zastanowieniu można bowiem stwierdzić, że niemal identyczny efekt można osiągnąć dwoma innymi sposobami:

  • Po pierwsze, możemy zdefiniować klasę pochodną po tej, której chcemy dodać metodę. Albo wręcz zdefiniować sobie po prostu zwykłą funkcję przyjmującą jeszcze zwyklejszy parametr i mieć coś w stylu StringUtils.WordCount(s). Jak widać, co najmniej jeden z tych sposobów można zastosować do każdej klasy – także takiej, której definicją nie dysponujemy (co w gruncie rzeczy jest banalne; na takich “sztuczkach” polega przecież całe programowanie obiektowe ;-]).
  • Po drugie, możemy zwyczajne pójść do deklaracji klasy i żądaną metodę dodać. Naturalnie, musi to być nasza własna klasa, której kodem dysponujemy i który możemy zmieniać.

Cechą wspólną obu tych “obejść” jest to, iż to my chcemy mieć tę nową metodę i to my ją definiujemy – zapewne dlatego, że to my chcemy jej potem używać. A skoro tak, to mamy wolność w ustaleniu sposobu jej wywoływania, ergo: nie ma znaczenia, jak ją zdefiniujemy. Któryś z tych dwóch/trzech sposobów będzie więc wystarczający.

Czyżby więc wychodziło na to, że C# w końcu dorobił się feature‘a, który rzeczywiście niczemu nie służy i jest kompletnie bez sensu? :) Prawie… Jak to z większością nowości w C# 3.0, przyczyna wprowadzenia metod rozszerzających streszcza się w – za przeproszeniem – czterech literach: LINQ.
LINQ opiera się bowiem na rozszerzeniu interfejsów pojemników (np. IEnumerable) o dodatkowe metody służące wykonywaniu na nich quasi-SQL-owych zapytań – jak np. Select czy Where. One właśnie zostały zaimplementowane jako metody rozszerzające. A że ponadto trafiły one do przestrzeni nazw System.Linq, to stają się dostępne dopiero po zadeklarowaniu użycia tej właśnie przestrzeni.
Reasumując: twórcy .NET-a dodali dwie nowości (metody rozszerzające oraz LINQ) po to, żeby ta pierwsza pozwalała na nieużywanie tej drugiej. Genialne, czyż nie? ;D

Tags: ,
Author: Xion, posted under Programming »


6 comments for post “Metody rozszerzające w C#”.
  1. Marcinii:
    September 11th, 2009 o 12:51

    Pominąłeś jeden bardzo istotny aspekt metod rozszerzających, jeżeli stworzyliśmy metodę rozszerzającą dla typu String to poprzez mechanizm refleksji możemy odnaleźć tą metodę co NIE jest możliwe tworząc “metodę rozszerzającą” w inny wymieniony przez Ciebie sposób.
    Z kolei mechanizm refleksji to potężne narzędzie – o czym można dłużej pisać w osobnym artykule.

    Pozdrawiam
    Marcin Iwanowski
    Microsoft Certified Technology Specialist

  2. Asmodeusz:
    September 11th, 2009 o 13:26

    Podobne rozwiązanie istnieje już od dość dawna w Objective-C. Ponadto nie doceniasz jego funkcjonalności: np. pisząc własną serializację itp. po prostu tworzymy metodę rozszerzającą Serialize i z niej korzystamy. O skróceniu i uładnieniu kodu już nie wspominam ;-)
    Prosta konstrukcja:
    List serialized = (form o in Objects select o.Serialize()).ToList();
    Daje nam listę zserializowanych (naszym serializerem) obiektów.

  3. Xion:
    September 14th, 2009 o 9:04

    Jeśli chodzi o refleksje, to widzę tutaj pewien problem. Załóżmy mianowicie, że mamy taką oto funkcję w stylu:

    1. public static class Introspect
    2. {
    3.     public static bool ObjectHasMethod(object obj, string method)
    4.     {
    5.         return obj.GetType().GetMethod(method) != null;
    6.     }
    7. }

    i wywołujemy ją dla obiektu z metodami rozszerzającymi – np. string z przykładu w notce. Co zostanie zwrócone dla Introspect.ObjectHasMethod(s, "WordCount"), jeśli:

    – klasa z metodami rozszerzającymi System.String jest w innej namespace, która nie jest usingowana w pliku z definicją klasy Introspect

    – jw., ale tym razem klasa ta jest w innym assembly

    – w końcu, klasa ta jest na innej maszynie, od której obiekt s otrzymujemy w wyniku serializacji, przesłania przez sieć i deserializacji

    Intuicyjnie spodziewam się wyniku true w pierwszym przypadku, prawdopodobnie true (acz z wątpliwościami i zapewne zastrzeżeniem, że odpowiednie assembly musi być załadowane w aplikacji) w drugim i chyba false w trzecim. Nie da się jednak ukryć, że sytuacja, gdy ta sama klasa ma inną definicję w zależności od miejsca, w której jej używamy, może być myląca.

  4. Dabroz:
    September 16th, 2009 o 2:51

    Ja bym zwrócił uwagę na co innego.

    public static class TaKlasaRozszerzaStringaAleNieSluzyDoNiczegoSensownegoNiechWiecNazywaSieNaPrzykladZdjecieKrolikaZNalesnikiemNaGlowieAKAFailProjektowyCSharpaFoooo….oooobar
    {
    ….
    }

    ;)

  5. Force:
    September 16th, 2009 o 22:08

    To teraz wypada wspomnieć o class helperach w Delphi (od wersji 2006) i Delphi for Net (od wersji 8). Ktoś musi domagać się danych o Delphi :P Jest to coś podobnego ale definiuje się klasę będącą klasą helperem i np. this-a (Self-a) nie definiuje się w metodach ale jest domyślnie (klasa anie metody nie są statyczne) i to co się rozszerza jest w definicji klasy

  6. Asmodeusz:
    September 17th, 2009 o 18:02

    @Xion: oczywiście, że różna definicja metody, zależnie od platformy jest jak najbardziej na miejscu.
    Weźmy na to tworzymy sobie dla naszej formy metodę rozszerzającą (w EXEku) wyświetlającą stale widoczne powiadomienie. Na Windows będzie to tray icon, na Windows Mobile – home screen plugin. Na linuksie znów coś innego – a w kodzie ta sama metoda ;-)

Comments are disabled.
 


© 2019 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.