Почему ключевое слово 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требуется ключевое слово? Или без него работает, а что-то упускаю?

(Думаю, должен быть дубликат этого вопроса, но мне не удалось его найти)

Обновление :

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

M4N
источник
@ M4N - Это похоже на возможный дубликат, который вы искали
djdd87
@GenericTypeTea: да, это аналогичный вопрос. Но я не спрашиваю, как вызвать метод расширения (я знаю, что мне нужно добавить thisключевое слово), я спрашиваю, зачем это thisключевое слово требуется. Я обновил свой вопрос.
M4N
@ M4N - Хороший момент, они говорят, что это нужно сделать, но ответы на самом деле не дают достаточно хорошего объяснения, почему.
djdd87
В методах экземпляра this неявно передается методу. Вызывая Method (), а не this.Method () или Method (this), вы не сообщаете компилятору, что передать методу.
Алекс Хамфри,
@Alex. Технически я полагаю, что компилятор мог бы сделать вывод «this» в контексте вызова Method (), однако я думаю, что это был бы плохой выбор дизайна и сделал бы код менее читаемым.
Бен Робинсон

Ответы:

55

Пара очков:

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

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

Если вы находитесь в сценарии , где вы используете метод расширения для типа внутри этого типа , то вы действительно имеете доступ к исходному коду. Тогда почему вы вообще используете метод расширения? Вы можете написать метод экземпляра самостоятельно, если у вас есть доступ к исходному коду расширенного типа, и тогда вам вообще не нужно использовать метод расширения! Затем ваша реализация может воспользоваться преимуществами доступа к частному состоянию объекта, чего не могут добиться методы расширения.

Упрощение использования методов расширения внутри типа, к которому у вас есть доступ, поощряет использование методов расширения вместо методов экземпляра. Методы расширения - это здорово, но обычно лучше использовать метод экземпляра, если он у вас есть.

С учетом этих двух моментов на разработчика языка больше не ложится бремя объяснения, почему эта функция не существует. Теперь вам нужно объяснить, почему это должно происходить . С функциями связаны огромные затраты. Эта функция не является необходимой и работает против заявленных целей разработки методов расширения; почему мы должны брать на себя расходы по его внедрению? Объясните, какой привлекательный и важный сценарий позволяет использовать эту функцию, и мы рассмотрим возможность ее реализации в будущем. Я не вижу убедительного и важного сценария, который бы это оправдал, но, возможно, есть один, который я пропустил.

Эрик Липперт
источник
11
Спасибо за такой ответ! Два момента: 1: я не просил реализовать эту функцию, просто для объяснения, почему ее нет / почему thisона требуется. 2: Почему я вызываю метод расширения из расширенного типа? В приведенном примере я добавляю несколько удобных методов к базовому классу (ViewPage) и вызываю его из производных классов. Это избавляет от необходимости создавать общий базовый класс для всех моих представлений. Кстати: я согласен с тем, что использовать метод расширения из расширенного типа неудобно, но снова посмотрите пример с MVC ViewPage.
M4N
3
@ M4N: это точно моя ситуация: я хочу расширить метод UserControl и сделать это расширение доступным для всех моих UserControls, без явного 'this'. Я знаю, что разработка компиляторов - это не демократия, но считаю это моим голосом за неявное «это» :-)
Дункан Бейн
8
Привет, Эрик, это не основной сценарий. Однако общий шаблон выглядит так: 1. У вас нет доступа для редактирования исходного кода базового класса. 2. вы хотите добавить некоторые (защищенные) методы к базовому типу, которые вы хотите вызывать в телах производных классов. Есть ли другой механизм для этого? this.MethodName не так уж сложно набирать, но через некоторое время он начинает надоедать ...
Гишу
5
Я думаю, что сценарий добавления метода расширения к базовому классу (для которого у вас нет источника) является допустимым. В этом случае было бы неплохо иметь возможность вызывать метод без «this». Кстати, это может быть решено в следующей версии С # с помощью «статического импорта».
лизергиновая кислота
4
Одной из веских причин для использования методов расширения из класса, который вы расширяете, является наличие нескольких типов, реализующих интерфейс. Например, у вас есть два класса, которые реализуют ICrossProductable, и вы определяете метод расширения для этого вызываемого интерфейса GetProduct. Очень простой пример, но я часто нахожу его полезным. Однако это не лучший подход во всех случаях.
Ян Ньюсон
9

Без него компилятор просто видит его как статический метод в статическом классе, который принимает страницу в качестве первого параметра. т.е.

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

vs.

// with 'this'
string s = page.Method();
Ферруччо
источник
1
Я думаю, что это должен быть ViewExtensions.Method (page).
Дэйв Ван ден Эйнде,
6

В методах экземпляра this неявно передается каждому методу прозрачно, поэтому вы можете получить доступ ко всем членам, которые он предоставляет.

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

Вы можете сказать: «Почему он просто не понимает, что такое вызывающий объект, и не передает его в качестве параметра?»

Ответ заключается в том, что методы расширения статичны и могут быть вызваны из статического контекста, где нет this.

Я предполагаю, что они могли бы проверить это во время компиляции, но, честно говоря, это, вероятно, большая работа с очень небольшой отдачей. И, честно говоря, я не вижу особой пользы в том, чтобы убрать некоторую часть явности вызовов методов расширения. Тот факт, что они могут ошибаться, например, в методах, означает, что иногда они могут быть совершенно не интуитивно понятными (например, исключение NullReferenceExceptions не генерируется). Иногда мне кажется, что им следовало ввести новый оператор стиля «прямая передача» для методов расширения.

Алекс Хамфри
источник
Так что, похоже, они сделали действительно плохой дизайнерский выбор, позволив ему вызываться из статического контекста, поскольку я не понимаю, зачем кому-то вообще это делать.
AustinWBryan
3

Важно отметить, что существуют различия между методами расширения и обычными методами. Думаю, вы только что столкнулись с одним из них.

Я приведу вам пример еще одного отличия: довольно легко вызвать метод расширения для nullссылки на объект. К счастью, обычными методами это сделать намного сложнее. (Но это можно сделать. IIRC, Джон Скит продемонстрировал, как это сделать, манипулируя кодом CIL.)

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

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

При этом я согласен с тем, что thisвызов метода расширения кажется немного нелогичным . В конце концов, в идеале метод расширения должен «ощущаться» и вести себя так же, как и нормальный.

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

stakx - больше не участвует
источник
на языке Boo вы можете вызывать метод или свойство Extension непосредственно для null, то есть null.Do ().
Сергей Мирвода
1
@Sergey: Звучит опасно ... :) Мне интересно, откуда Бу знает (подразумеваемый) тип null? Может быть практически любой ссылочный тип, как он узнает, какие методы доступны null?
stakx - больше не вносит вклад
1
Может быть веская причина для возможности вызвать метод расширения с нулевой ссылкой.
Дэйв Ван ден Эйнде,
@DaveVandenEynde: IMHO, для .net было ошибкой не определять атрибут для невиртуальных методов, которые потребовали бы, чтобы компиляторы вызывали их с невиртуальными вызовами. Некоторые неизменяемые типы классов, например, Stringлогически ведут себя как значения и могут иметь разумное значение по умолчанию, когда null (например, .net может последовательно рассматривать нулевую ссылку статического типа stringкак пустую строку, а не делать это только спорадически). Необходимость использовать статические методы для таких вещей if (!String.IsNullOrEmpty(stringVar))просто ужасно.
supercat
1
@supercat Да, ужасно. Вы также можете выполнить string.IsNullOrEmpty (). Намного красивее.
Дэйв Ван ден Эйнде
1

Поскольку метод расширения не существует с классом ViewPage. Вам нужно сообщить компилятору, для чего вы вызываете метод расширения. Помните, что this.Method()это просто сахар компилятора ViewExtensions.Method(this). Точно так же нельзя просто вызвать метод расширения в середине любого класса по имени метода.

Бен Робинсон
источник
1
В этом есть смысл (каким-то образом). Но если это сахар компилятора, то почему компилятор не может проверить методы расширения без thisключевого слова?
M4N
Мне кажется, это недосмотр. Как вы сказали, компилятор может увидеть, что метод не существует, а затем проверить наличие расширений.
TomTom
Да, возможно, это сработает. Я подозреваю, что это конкретное дизайнерское решение, чтобы сделать код более читабельным. Если бы вы могли просто вызвать ExtensionMethod () внутри класса, это могло бы немного сбить с толку кого-то, кто читает код, если этот класс не содержит этот метод, но с this.ExtensionMethod (), по крайней мере, это согласуется с использованием MyInstance.ExtentionMethod ()
Бен Робинсон
Я думаю, что this.Method () больше похож на синтаксический сахар для ViewExtensions.Method (this).
Алекс Хамфри,
1

Я работаю над плавным API и столкнулся с той же проблемой. Несмотря на то, что у меня есть доступ к классу, который я расширяю, я все же хотел, чтобы логика каждого из свободных методов находилась в их собственных файлах. Ключевое слово «this» было очень неинтуитивным, пользователи продолжали думать, что метод отсутствовал. Что я сделал, так это сделал мой класс частичным классом, который реализовал нужные мне методы вместо использования методов расширения. Я не увидел в ответах упоминания о частичностях. Если у вас есть этот вопрос, то партиалы могут быть лучшим вариантом.

Рейн
источник