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:
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:
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:
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 ;-]).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
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
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.
Jeśli chodzi o refleksje, to widzę tutaj pewien problem. Załóżmy mianowicie, że mamy taką oto funkcję w stylu:
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 using
owana 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.
Ja bym zwrócił uwagę na co innego.
public static class TaKlasaRozszerzaStringaAleNieSluzyDoNiczegoSensownegoNiechWiecNazywaSieNaPrzykladZdjecieKrolikaZNalesnikiemNaGlowieAKAFailProjektowyCSharpaFoooo….oooobar
{
….
}
;)
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
@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 ;-)