Чому для виклику методу розширення з розширеного класу потрібно ключове слово 'this'


79

Я створив метод розширення для ASP.NET MVC ViewPage, наприклад:

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

При виклику цього методу з подання (що походить від ViewPage), я отримую помилку " CS0103: ім'я" Метод "не існує в поточному контексті ", якщо я не використовую thisключове слово для його виклику:

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

Чому thisпотрібне ключове слово? Або це працює без цього, але я чогось пропускаю?

(Я думаю, що це питання має бути копією, але мені не вдалося його знайти)

Оновлення :

Як каже Бен Робінсон , синтаксис методів розширення викликів - це просто компілятор. Тоді чому компілятор не може автоматично перевірити наявність методів розширення базових типів поточного типу, не вимагаючи цього ключового слова?


@ M4N - Це схоже на можливий дублікат, який ви шукали
djdd87,

@GenericTypeTea: так, це схоже питання. Але я не запитую, як викликати метод розширення (я знаю, що я повинен додати thisключове слово), але я запитую, чому thisключове слово потрібно. Я оновив своє запитання.
M4N

@ M4N - Хороша думка, вони кажуть, що це потрібно зробити, але відповіді насправді не дають достатньо вагомих пояснень, чому.
djdd87,

У методах екземпляра "це" неявно передається методу. Викликаючи Method (), а не this.Method () або Method (this), ви не говорите компілятору, що передати методу.
Алекс Хамфрі,

@Alex. Технічно я гадаю, що компілятор міг би зробити висновок про "це" в контексті виклику Method (), однак я думаю, що це було б поганим вибором дизайну і зробило б код менш читабельним.
Бен Робінсон,

Відповіді:


55

Пара очок:

По-перше, пропонована функція (неявне "this." При виклику методу розширення) непотрібна . Методи розширення були необхідні для того, щоб розуміння запитів LINQ працювало так, як ми хотіли; одержувач завжди вказується у запиті, тому не потрібно підтримувати це неявно, щоб змусити LINQ працювати.

По-друге, функція працює проти більш загальної конструкції методів розширення: а саме, що методи розширення дозволяють вам розширити тип, який ви не можете розширити самостійно , або тому, що це інтерфейс, і ви не знаєте реалізації, або тому, що ви знаєте знаєте реалізацію, але не маєте вихідного коду.

Якщо ви перебуваєте в сценарії , де ви використовуєте метод розширення для типу всередині цього типу , то ви дійсно маєте доступ до вихідного коду. Чому тоді ви спочатку використовуєте метод розширення? Ви можете написати метод екземпляра самостійно, якщо у вас є доступ до вихідного коду розширеного типу, і тоді вам зовсім не доведеться використовувати метод розширення! Тоді ваша реалізація може скористатися перевагами доступу до приватного стану об’єкта, а методи розширення не можуть.

Спрощення використання методів розширення всередині типу, до якого ви маєте доступ, заохочує використання методів розширення над методами екземпляра. Методи розширення чудові, але, як правило, краще використовувати метод екземпляра, якщо він у вас є.

Враховуючи ці два моменти, тягар більше не лягає на розробника мови, щоб пояснити, чому ця функція не існує. Зараз вам належить пояснити, чому це потрібно . Функції пов’язані з величезними витратами. Ця функція не є необхідною і працює проти заявлених цілей проектування методів розширення; чому ми повинні брати на себе витрати на його впровадження? Поясніть, який привабливий, важливий сценарій увімкнено цією функцією, і ми розглянемо можливість її впровадження в майбутньому. Я не бачу жодного вагомого, важливого сценарію, який би це виправдовував, але, можливо, є такий, який я пропустив.


11
Дякую за цю відповідь! Два моменти: 1: Я не просив, щоб ця функція була реалізована, лише для пояснення, чому її немає / чому thisце потрібно. 2: Чому я викликаю метод розширення з розширеного типу? У наведеному прикладі я додаю деякі зручні методи до базового класу (ViewPage) і викликаю його з похідних класів. Це виключає необхідність створення загального базового класу для всіх моїх поглядів. BTW: Я згоден, що використовувати метод розширення з розширеного типу незручно, але знову подивіться приклад з MVC ViewPage.
M4N

3
@ M4N: це точно також моя ситуація: я хочу розширити метод UserControl і зробити це розширення доступним для всіх моїх UserControls, без явного "this". Я знаю, що розробка компілятора не є демократією, але вважайте це моїм голосуванням за неявне "це" :-)
Duncan Bayne

8
Привіт, Ерік! Звичайно, це не основний сценарій. Однак загальний шаблон, здається, дорівнює 1. Ви не маєте доступу до редагування джерела до базового класу. 2. ви хочете додати деякі (захищені) методи до базового типу, який ви хочете викликати в тілах похідних класів. Чи існує якийсь інший механізм для досягнення цього? this.MethodName не так складно друкувати, але через деякий час це дратує ...
Gishu

5
Я думаю, що сценарій додавання методу розширення до базового класу (для якого у вас немає джерела) є допустимим. У такому випадку було б непогано мати можливість викликати метод без "цього". До речі, це може бути вирішено в наступній версії c # зі "статичним імпортом".
лізергінова кислота

4
Однією з вагомих причин використання методів розширення з класу, який ви розширюєте, є наявність декількох типів, які реалізують інтерфейс. Наприклад, у вас є два класи, які реалізують ICrossProductable, і ви визначаєте метод розширення для того інтерфейсу, який називається GetProduct. Дуже простий приклад, але я часто вважаю це корисним. Однак це не найкращий підхід у всіх випадках.
Ян Ньюсон,

9

Без цього компілятор просто розглядає його як статичний метод у статичному класі, який приймає сторінку як перший параметр. тобто

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

проти

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

1
Я думаю, що це має бути ViewExtensions.Method (сторінка).
Dave Van den Eynde

6

У методах екземплярів `` це '' неявно передається кожному методу прозоро, тому ви можете отримати доступ до всіх членів, які він надає.

Методи розширення є статичними. Викликаючи Method()замість this.Method()або Method(this), ви не повідомляєте компілятору, що передати методу.

Ви можете сказати: "чому він просто не усвідомлює, що таке викличний об'єкт, і не передає це як параметр?"

Відповідь полягає в тому, що методи розширення є статичними і їх можна викликати із статичного контексту, де немає "цього".

Думаю, вони могли перевірити це під час компіляції, але, чесно кажучи, це, мабуть, багато роботи за надзвичайно малу виплату. І, чесно кажучи, я бачу невелику користь у вилученні певної чіткості викликів методу розширення. Той факт, що їх можна помилково сприйняти як методи прикладу, означає, що вони часом можуть бути досить неінтуїтивними (наприклад, NullReferenceExceptions не викидаються). Іноді я думаю, що їм слід було запровадити новий оператор стилю "труба вперед" для методів розширення.


Отже, схоже, вони зробили по-справжньому поганий вибір дизайну, щоб тоді його можна було викликати із статичного контексту, оскільки я не розумію, чому хтось коли-небудь це робив.
AustinWBryan

3

Важливо зазначити, що існують відмінності між методами розширення та звичайними методами. Я думаю, ви щойно натрапили на одного з них.

Я наведу вам приклад іншої різниці: досить просто викликати метод розширення за nullпосиланням на об'єкт. На щастя, це набагато складніше зробити звичайними методами. (Але це можна зробити. IIRC, Джон Скіт продемонстрував, як це зробити, маніпулюючи кодом CIL.)

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

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

Враховуючи це, я погоджуюсь, що це виглядає трохи нелогічно, що thisпотрібно викликати метод розширення. Зрештою, метод розширення в ідеалі повинен "відчувати" і поводитися так само, як звичайний.

Але насправді методи розширення більше нагадують синтаксичний цукор, доданий поверх існуючої мови, ніж ранню основну функцію, яка чудово вписується в мову в усіх відношеннях.


мовою Boo ви можете викликати метод або властивість Extension безпосередньо на null, тобто null.Do ().
Сергій Мирвода

1
@Sergey: Звучить небезпечно ... :) Мені цікаво, як Бу знає (мається на увазі) тип null? Це може бути майже будь-який посилальний тип, звідки він знає, які методи доступні null?
stakx - більше не вносить внесок

1
Може бути вагома причина, щоб мати можливість викликати метод розширення з нульовим посиланням.
Dave Van den Eynde

@DaveVandenEynde: IMHO, помилкою для .net було не визначення атрибуту для невіртуальних методів, що вимагало б, щоб компілятори викликали їх за допомогою невіртуальних викликів. Деякі незмінні типи класів, такі як Stringлогічно, поводяться як значення, і можуть мати розумне значення за замовчуванням, коли дорівнює нулю (наприклад .net може постійно розглядати нульове посилання статичного типу stringяк порожній рядок, а не робити це лише епізодично). Доводиться використовувати статичні методи для таких речей, як if (!String.IsNullOrEmpty(stringVar))просто жахливо.
supercat

1
@supercat Так, огидно. Ви також можете зробити string.IsNullOrEmpty (). Набагато гарніші.
Dave Van den Eynde

1

Оскільки метод розширення не існує з класом ViewPage. Вам потрібно сказати компілятору, до чого ви викликаєте метод розширення. Пам'ятайте, this.Method()це просто компілятор цукру для ViewExtensions.Method(this). Таким же чином ви не можете просто викликати метод розширення в середині будь-якого класу за назвою методу.


1
Це має сенс (якось). Але якщо це компілятор цукру, то чому компілятор не може перевірити методи розширення без thisключового слова?
M4N

Мені це здається недоглядом. Як ви вже сказали - компілятор міг побачити, що метод не існує, а потім перевірити розширення на наявність.
TomTom

Так, це може спрацювати. Я підозрюю, що це конкретне дизайнерське рішення, щоб зробити код більш читабельним. Якщо ви можете просто викликати ExtensionMethod () у класі, це може трохи заплутати когось, хто читає код, якщо цей клас не містить цього методу, але з цим.
Бен Робінсон,

Я думаю, що this.Method () більше схожий на синтаксичний цукор для ViewExtensions.Method (this).
Алекс Хамфрі,

1

Я працюю над вільним API і зіткнувся з тим же питанням. Незважаючи на те, що я маю доступ до класу, який я розширюю, я все одно хотів, щоб логіка кожного з вільних методів була у своїх власних файлах. Ключове слово "це" було дуже неінтуїтивним, користувачі продовжували думати, що метод відсутній. Що я зробив, це зробив свій клас частковим класом, який реалізував потрібні мені методи, а не використовував методи розширення. У відповідях я не побачив згадки про частковість. Якщо у вас є це запитання, частковим варіантом може бути кращий варіант.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.