Dlaczego słowo kluczowe „this” jest wymagane do wywołania metody rozszerzenia z klasy rozszerzonej

79

Utworzyłem metodę rozszerzenia dla ASP.NET MVC ViewPage, np .:

public static class ViewExtensions
{
    public static string Method<T>(this ViewPage<T> page) where T : class
    {
        return "something";
    }
}

Podczas wywoływania tej metody z widoku (pochodzącego z ViewPage) otrzymuję błąd „ CS0103: Nazwa„ Metoda ”nie istnieje w bieżącym kontekście ”, chyba że użyjęthis słowa kluczowego, aby ją wywołać:

<%: Method() %> <!-- gives error CS0103 -->
<%: this.Method() %> <!-- works -->

Dlaczego thiswymagane jest słowo kluczowe? A może działa bez niego, ale czegoś mi brakuje?

(Myślę, że musi istnieć duplikat tego pytania, ale nie udało mi się go znaleźć)

Aktualizacja :

Jak mówi Ben Robinson , składnia wywołania metod rozszerzających to po prostu cukier kompilatora. Dlaczego więc kompilator nie może automatycznie sprawdzić metod rozszerzających dla typów podstawowych bieżącego typu bez wymagania słowa kluczowego this?

M4N
źródło
@ M4N - To wygląda na możliwy duplikat, którego szukałeś
djdd87
@GenericTypeTea: tak, to podobne pytanie. Ale nie pytam, jak wywołać metodę rozszerzenia ( wiem, że muszę dodać thissłowo kluczowe), ale pytam, dlaczego thissłowo kluczowe jest wymagane. Zaktualizowałem moje pytanie.
M4N
@ M4N - Słuszna uwaga, mówią, że trzeba to zrobić, ale odpowiedzi tak naprawdę nie dają wystarczająco dobrego wyjaśnienia, dlaczego.
djdd87
W przypadku metod instancji „this” jest niejawnie przekazywane do metody. Wywołując Method () zamiast this.Method () lub Method (this), nie mówisz kompilatorowi, co ma przekazać do metody.
Alex Humphrey
@Alex. Technicznie rzecz biorąc, wyobrażam sobie, że kompilator mógłby wywnioskować „to” w kontekście wywołania metody Method (), jednak myślę, że byłby to zły wybór projektowy i uczyniłby kod mniej czytelnym.
Ben Robinson

Odpowiedzi:

55

Kilka punktów:

Po pierwsze, proponowana funkcja (niejawne „this” w wywołaniu metody rozszerzenia) jest niepotrzebna . Metody rozszerzające były niezbędne, aby funkcje składane zapytań LINQ działały tak, jak chcieliśmy; odbiorca jest zawsze określony w zapytaniu, więc nie jest konieczne obsługiwanie tego niejawnego, aby LINQ działał.

Po drugie, ta funkcja działa przeciwko bardziej ogólnemu projektowi metod rozszerzających: a mianowicie, że metody rozszerzające pozwalają rozszerzyć typ, którego nie możesz rozszerzyć samodzielnie , ponieważ jest to interfejs i nie znasz implementacji, lub dlatego, że tak znasz implementację, ale nie masz kodu źródłowego.

Jeśli jesteś w sytuacji, w której używasz metodę rozszerzenia dla typu w tego typu potem nie mieć dostępu do kodu źródłowego. Dlaczego więc w pierwszej kolejności używasz metody rozszerzenia? Możesz napisać metodę instancji jeśli masz dostęp do kodu źródłowego typu rozszerzonego, a następnie nie musisz w ogóle używać metody rozszerzającej! Twoja implementacja może wtedy skorzystać z dostępu do prywatnego stanu obiektu, czego nie mogą uzyskać metody rozszerzające.

Ułatwienie korzystania z metod rozszerzających z poziomu typu, do którego masz dostęp, zachęca do używania metod rozszerzających zamiast metod instancji. Metody rozszerzające są świetne, ale zwykle lepiej jest użyć metody instancji, jeśli taką masz.

Biorąc pod uwagę te dwa punkty, projektant języka nie musi już dłużej wyjaśniać, dlaczego ta funkcja nie istnieje. Teraz musisz wyjaśnić, dlaczego tak się dzieje . Funkcje wiążą się z ogromnymi kosztami. Ta funkcja nie jest konieczna i działa przeciwko określonym celom projektowym metod rozszerzających; dlaczego mielibyśmy ponosić koszty jego wdrożenia? Wyjaśnij, jaki fascynujący, ważny scenariusz umożliwia ta funkcja, a rozważymy jej wdrożenie w przyszłości. Nie widzę żadnego przekonującego, ważnego scenariusza, który to uzasadnia, ale być może jest taki, który przegapiłem.

Eric Lippert
źródło
11
Dzięki za odpowiedź! Dwie kwestie: 1: Nie prosiłem o zaimplementowanie tej funkcji, tylko o wyjaśnienie, dlaczego jej nie ma / dlaczego thisjest wymagana. 2: Dlaczego wywołuję metodę rozszerzenia z typu rozszerzonego? W podanym przykładzie dodaję kilka wygodnych metod do klasy bazowej (ViewPage) i wzywam ją z klas pochodnych. Eliminuje to potrzebę tworzenia wspólnej klasy bazowej dla wszystkich moich widoków. BTW: Zgadzam się, że użycie metody rozszerzenia z rozszerzonego typu jest niewygodne, ale ponownie zobacz przykład z MVC ViewPage.
M4N
3
@ M4N: to jest dokładnie moja sytuacja: chcę rozszerzyć metodę UserControl i mieć to rozszerzenie dostępne dla wszystkich moich UserControls, bez jawnego „this”. Wiem, że rozwój kompilator nie jest demokracją, ale uznają to mój głos na niejawny „tym” :-)
Duncan Bayne
8
Cześć Eric, przyznaję, że to nie jest podstawowy scenariusz. Jednak ogólny wzorzec wydaje się być 1. Nie masz dostępu do edycji źródła do klasy bazowej. 2. chcesz dodać niektóre (chronione) metody do typu podstawowego, które chcesz wywołać w treści klas pochodnych. Czy istnieje inny mechanizm umożliwiający to osiągnięcie? this.MethodName nie jest trudne do wpisania, ale po chwili staje się denerwujące ...
Gishu
5
Myślę, że scenariusz dodawania metody rozszerzającej do klasy bazowej (do której nie masz źródła) jest prawidłowy. W takim przypadku byłoby miło móc wywołać metodę bez „this”. BTW można to rozwiązać w następnej wersji C # za pomocą „statycznych importów”.
kwas lizergowy
4
Jednym z ważnych powodów używania metod rozszerzających z klasy, którą rozszerzasz, jest sytuacja, gdy masz wiele typów, które implementują interfejs. Np. Masz dwie klasy, które implementują ICrossProductablei definiujesz metodę rozszerzającą w tym interfejsie o nazwie GetProduct. Bardzo prosty przykład, ale często uważam go za przydatny. Nie jest to jednak najlepsze podejście we wszystkich przypadkach.
Ian Newson
9

Bez tego kompilator widzi ją jako metodę statyczną w klasie statycznej, która przyjmuje page jako pierwszy parametr. to znaczy

// without 'this'
string s = ViewExtensions.Method(page);

vs.

// with 'this'
string s = page.Method();
Ferruccio
źródło
1
Myślę, że to powinno być ViewExtensions.Method (strona).
Dave Van den Eynde
6

W metodach instancji wartość „this” jest niejawnie przekazywana do każdej metody w sposób przejrzysty, dzięki czemu można uzyskać dostęp do wszystkich elementów, które udostępnia.

Metody rozszerzające są statyczne. Wywołując Method()zamiast this.Method()or Method(this), nie mówisz kompilatorowi, co ma przekazać do metody.

Możesz powiedzieć „dlaczego po prostu nie zdaje sobie sprawy, czym jest obiekt wywołujący i nie przekazuje tego jako parametru?”.

Odpowiedź jest taka, że ​​metody rozszerzające są statyczne i można je wywołać z kontekstu statycznego, w którym nie ma „tego”.

Wydaje mi się, że mogliby to sprawdzić podczas kompilacji, ale szczerze mówiąc, to prawdopodobnie dużo pracy za wyjątkowo małą zapłatę. Szczerze mówiąc, widzę niewielką korzyść w pozbyciu się niektórych jawności wywołań metod rozszerzających. Fakt, że można je pomylić na przykład z metodami, oznacza, że ​​czasami mogą być dość nieintuicyjne (na przykład NullReferenceExceptions nie są zgłaszane). Czasami myślę, że powinni byli wprowadzić nowy operator typu „potok do przodu” dla metod rozszerzających.

Alex Humphrey
źródło
Wygląda więc na to, że dokonali naprawdę złego wyboru projektowego, aby móc go wtedy wywołać ze statycznego kontekstu, ponieważ nie rozumiem, dlaczego ktokolwiek miałby to kiedykolwiek zrobić.
AustinWBryan,
3

Należy zauważyć, że istnieją różnice między metodami rozszerzającymi a zwykłymi metodami. Myślę, że właśnie spotkałeś jedną z nich.

Podam przykład innej różnicy: dość łatwo jest wywołać metodę rozszerzającą w nullodniesieniu do obiektu. Na szczęście jest to o wiele trudniejsze przy zwykłych metodach. (Ale można to zrobić. IIRC, Jon Skeet zademonstrował, jak to zrobić, manipulując kodem CIL).

static void ExtensionMethod(this object obj) { ... }

object nullObj = null;
nullObj.ExtensionMethod();  // will succeed without a NullReferenceException!

Biorąc to pod uwagę, zgadzam się, że wydaje się to trochę nielogiczne this wywołanie metody rozszerzenia . W końcu metoda rozszerzająca powinna idealnie „czuć” i zachowywać się jak normalna.

Ale w rzeczywistości metody rozszerzeń są bardziej jak cukier składniowy dodawany do istniejącego języka niż wczesna podstawowa funkcja, która dobrze pasuje do języka pod każdym względem.

stakx - już nie wnoszący wkładu
źródło
w języku Boo można wywołać metodę lub właściwość rozszerzenia bezpośrednio na null, czyli null.Do ().
Sergey Mirvoda
1
@Sergey: Brzmi niebezpiecznie ... :) Ciekawe, skąd Boo zna (domniemany) typ null? Może to być dowolny typ referencyjny, skąd wie, jakie metody są dostępne null?
stakx - już nie publikuje
1
Może istnieć dobry powód, aby móc wywołać metodę rozszerzenia z odwołaniem o wartości null.
Dave Van den Eynde
@DaveVandenEynde: IMHO, błędem było, że .net nie definiował atrybutu dla metod niewirtualnych, które wymagałyby, aby kompilatory wywoływały je za pomocą wywołań niewirtualnych. Niektóre niezmienne typy klas, takie jak Stringlogicznie zachowują się jak wartości, i mogą mieć rozsądną wartość domyślną, gdy null (np. .Net może konsekwentnie traktować zerową referencję typu statycznego stringjako pusty ciąg, zamiast robić to sporadycznie). Konieczność używania statycznych metod do takich rzeczy if (!String.IsNullOrEmpty(stringVar))jest po prostu ohydna.
supercat
1
@supercat Tak, ohydne. Możesz także zrobić string.IsNullOrEmpty (). O wiele ładniejsza.
Dave Van den Eynde
1

Ponieważ metoda rozszerzenia nie istnieje w klasie ViewPage. Musisz powiedzieć kompilatorowi, do czego wywołujesz metodę rozszerzenia. Pamiętaj, że this.Method()to tylko cukier kompilatora ViewExtensions.Method(this). W ten sam sposób nie można po prostu wywołać metody rozszerzającej w środku dowolnej klasy za pomocą nazwy metody.

Ben Robinson
źródło
1
To ma sens (w jakiś sposób). Ale jeśli jest to cukier kompilatora, dlaczego kompilator nie może sprawdzić metod rozszerzających bez thissłowa kluczowego?
M4N
Dla mnie wygląda na przeoczenie. Jak powiedziałeś - kompilator mógł zobaczyć, że metoda nie istnieje, następnie sprawdź dostępność rozszerzeń.
TomTom
Tak, prawdopodobnie to mogłoby zadziałać. Podejrzewałbym, że to konkretna decyzja projektowa, aby uczynić kod bardziej czytelnym. Gdybyś mógł po prostu wywołać ExtensionMethod () w klasie, może to być nieco mylące dla kogoś czytającego kod, jeśli ta klasa nie zawiera tej metody, ale z this.ExtensionMethod () przynajmniej jest to zgodne z użyciem MyInstance.ExtentionMethod ()
Ben Robinson
Myślę, że this.Method () jest bardziej jak cukier składniowy dla ViewExtensions.Method (this).
Alex Humphrey
1

Pracuję nad płynnym API i napotkałem ten sam problem. Mimo że mam dostęp do rozszerzanej klasy, nadal chciałem, aby logika każdej z metod Fluent znajdowała się we własnych plikach. Słowo kluczowe „this” było bardzo nieintuicyjne, użytkownicy wciąż myśleli, że brakuje metody. To, co zrobiłem, to uczynienie mojej klasy częściową klasą, która implementuje metody, których potrzebowałem, zamiast używać metod rozszerzających. W odpowiedziach nie było wzmianki o częściach podrzędnych. Jeśli masz to pytanie, częściowe mogą być lepszą opcją.

Reyn
źródło