Ответ 1
Рассмотрим эту ситуацию:
public class Base
{
public void BaseMethod()
{
}
}
public class Sub : Base
{
public void SubMethod()
{
}
}
public static class Extensions
{
public static void ExtensionMethod(this Base @base) { }
}
Вот несколько интересных утверждений об этом коде:
- Я не могу вызывать метод расширения с помощью
ExtensionMethod()
ни изBase
, ниSub
. - Я не могу позвонить
base.ExtensionMethod()
изSub
. - Я могу вызвать метод расширения с помощью
Extensions.ExtensionMethod(this)
как изSub
, так иBase
. - Я могу вызвать метод расширения с помощью
this.ExtensionMethod()
как изSub
, так иBase
.
Почему это?
У меня нет убедительного ответа, отчасти потому, что его может не быть: поскольку вы можете читать этот поток, вам нужно добавить this.
if вы хотите вызвать его в стиле метода расширения.
Когда вы пытаетесь использовать метод расширения из того типа, в котором он находится (или, следовательно, - из типа, полученного из типа, используемого в методе расширения), компилятор этого не понимает и будет пытаться для вызова его как статического метода без каких-либо аргументов.
Как говорится в ответе: они [разработчики языка] считают, что это не важный сценарий использования, чтобы поддерживать неявные методы расширения (чтобы дать зверю имя) изнутри этого типа, потому что он будет поощрять методы расширения, которые действительно должны быть экземпляров, и это считалось излишним.
Теперь трудно узнать, что происходит именно под обложками, но из некоторых игр мы можем сделать вывод, что base.X()
нам не помогает. Я могу только предположить, что base.X
выполняет свой виртуальный вызов как X()
, а не this.X()
из контекста базового класса.
Что мне делать, когда я хочу вызвать метод расширения базового класса из подкласса?
Честно говоря, я не нашел действительно элегантного решения. Рассмотрим этот сценарий:
public class Base
{
protected void BaseMethod()
{
this.ExtensionMethod();
}
}
public class Sub : Base
{
public void SubMethod()
{
// What comes here?
}
}
public static class Extensions
{
public static void ExtensionMethod(this Base @base)
{
Console.WriteLine ("base");
}
public static void ExtensionMethod(this Sub sub)
{
Console.WriteLine ("sub");
}
}
Есть 3 способа (оставляя в стороне отражение), чтобы вызвать перегрузку ExtensionMethod(Base)
:
- Вызов
BaseMethod()
, который формирует прокси-сервер между подклассом и методом расширения.
Вы можете использовать BaseMethod()
, base.BaseMethod()
и this.BaseMethod()
для этого, так как теперь вы просто имеете дело с обычным методом экземпляра, который, в свою очередь, вызовет метод расширения. Это довольно приемлемое решение, так как вы не загрязняете публичный API, но вы также должны предоставить отдельный метод, чтобы сделать что-то, что должно было быть доступно в контексте в первую очередь.
- Использование метода расширения как статического метода
Вы также можете использовать примитивный способ написания метода расширения, пропуская синтаксический сахар и перейдя прямо к тому, что он будет скомпилирован. Теперь вы можете передать параметр, чтобы компилятор не запутался. Очевидно, что мы передадим литую версию текущего экземпляра, чтобы мы настроили правильную перегрузку:
Extensions.ExtensionMethod((Base) this);
- Использовать - то, что должно быть идентичным переводом -
base.ExtensionMethod()
Это вдохновлено замечанием @Mike z о спецификации языка, в котором говорится следующее:
При привязке времени выражения базового доступа формы
base.I
иbase[E]
оцениваются точно так, как если бы они были написаны((B)this).I
и((B)this)[E]
, гдеB
- базовый класс класса или структура, в которой происходит построение. Таким образом,base.I
иbase[E]
соответствуютthis.I
иthis[E]
, кромеthis
рассматривается как экземпляр базового класса.
Спецификация буквально говорит, что base.I
будет вызываться как ((B) this).I
. Однако в нашей ситуации base.ExtensionMethod();
выдаст ошибку компиляции, а ((Base) this).ExtensionMethod();
будет работать отлично.
Похоже, что что-то не так, как в документации, так и в компиляторе, но этот вывод должен быть сделан кем-то с более глубокими знаниями в этом вопросе (paging Dr. Lippert).
Разве это не смущает?
Да, я бы так сказал. Это похоже на черную дыру в спецификации С#: практически все работает безупречно, но затем вам приходится перепрыгивать через некоторые обручи, потому что компилятор не знает, чтобы ввести текущий экземпляр в вызов метода в этом сценарии.
На самом деле, intellisense тоже путают эту ситуацию:
Мы уже определили, что этот вызов никогда не может работать, но intellisense полагает, что это возможно. Также обратите внимание, как он добавляет "using PortableClassLibrary" за именем, указывая, что будет добавлена директива using
. Это невозможно, потому что текущее пространство имен фактически PortableClassLibrary
. Но, конечно, когда вы действительно добавляете этот вызов метода:
и все работает не так, как ожидалось.
Возможно, вывод?
Основной вывод прост: было бы хорошо, если бы это нишевое использование методов расширения было бы поддержано. Главный аргумент в пользу его не был вызван тем, что он побуждал бы людей писать методы расширения вместо методов экземпляра.
Очевидная проблема здесь, конечно, в том, что у вас может не всегда быть доступ к базовому классу, который делает методы расширения a обязательным, но текущей реализацией это невозможно.
Или, как мы видели, не возможно с симпатичным синтаксисом.