Как переопределить существующий метод расширения
Я хочу заменить методы расширения, включенные в среду .NET или ASP MVC, своими собственными методами.
Пример
public static string TextBox(this HtmlHelper htmlHelper, string name)
{
...
}
Возможно ли это? Я не могу использовать ключевое слово override или new.
Ответы
Ответ 1
UPDATE: этот вопрос был тема моего блога в декабре 2013 года. Спасибо за отличный вопрос!
Вы можете сделать это, в некотором смысле. Но я должен начать с краткого обсуждения базового принципа проектирования разрешения перегрузки в С#. Разумеется, все разрешения перегрузки включают набор методов с тем же именем и выбор из этого набора уникального лучшего участника для вызова.
Существует множество факторов, определяющих, что является "лучшим" методом; на разных языках используется другая "смесь" факторов, чтобы понять это. С#, в частности, сильно "близость" данного метода к сайту вызова. Если задан выбор между применимым методом в базовом классе или новым применимым методом в производном классе, С# берет один из производного класса, потому что он ближе, даже если тот, который находится в базовом классе, в любом другом случае лучше совпадение.
Итак, мы закончили список. Производные классы ближе, чем базовые классы. Внутренние классы ближе, чем внешние классы. Методы в иерархии классов ближе, чем методы расширения.
И теперь мы подошли к вашему вопросу. Близость метода расширения зависит от (1), сколько пробелов в пространстве имен мы должны были уйти? и (2) нашел ли мы метод расширения через using
или был ли он прямо там в пространстве имен? Поэтому вы можете повлиять на разрешение перегрузки, изменив в пространстве имен ваш статический класс расширения, чтобы поместить его в более близкое пространство имен на сайт вызова. Или вы можете изменить свои объявления using
, чтобы поместить using
пространства имен, которое содержит желаемый статический класс, ближе другого.
Например, если у вас есть
namespace FrobCo.Blorble
{
using BazCo.TheirExtensionNamespace;
using FrobCo.MyExtensionNamespace;
... some extension method call
}
то это двусмысленно, что ближе. Если вы хотите расставить приоритеты по своему усмотрению, вы можете сделать это:
namespace FrobCo
{
using BazCo.TheirExtensionNamespace;
namespace Blorble
{
using FrobCo.MyExtensionNamespace;
... some extension method call
}
И теперь, когда разрешение перегрузки переходит к разрешению вызова метода расширения, классы в Blorple
получают сначала go, затем классы в FrobCo.MyExtensionNamespace
, затем классы в FrobCo
, а затем классы в BazCo.TheirExtensionNamespace
.
Это ясно?
Ответ 2
Методы расширения нельзя переопределить, поскольку они не являются методами экземпляра, и они не являются виртуальными.
Компилятор будет жаловаться, если вы импортируете оба класса метода расширения через пространство имен, поскольку он не будет знать, какой метод вызывать:
Вызов неоднозначен между следующими методами или свойствами:...
Единственный способ обойти это - вызвать метод расширения с использованием стандартного синтаксиса статического метода. Поэтому вместо этого:
a.Foo();
вам нужно будет сделать это:
YourExtensionMethodClass.Foo(a);
Ответ 3
Методы расширения - это в основном просто статические методы, поэтому я не знаю, как их можно переопределить, но если вы просто поместите их в другое пространство имен, вы можете позвонить вместо них, вместо того, который хотите заменить.
Но Мэтт Манела рассказывает о том, как методы экземпляра имеют приоритет над методами расширения:
http://social.msdn.microsoft.com/forums/en-US/csharplanguage/thread/e42f1511-39e7-4fed-9e56-0cc19c00d33d
Для получения дополнительных идей о методах расширения вы можете посмотреть http://gen5.info/q/2008/07/03/extension-methods-nulls-namespaces-and-precedence-in-c/
Изменить: я забыл о проблеме двусмысленности, поэтому лучше всего попытаться не включать методы расширения, которые вы хотите заменить. Таким образом, вам может потребоваться не использовать директиву "using", а просто указать полное имя пакета для некоторых классов, и это может решить проблему.
Ответ 4
Основываясь на предположении Эрика (и тот факт, что код представления отображается в пространстве имен ASP), вы должны иметь возможность переопределить его так (по крайней мере, он работает для меня в ASP.NET MVC4.0 Razor
using System.Web.Mvc;
namespace ASP {
public static class InputExtensionsOverride {
public static MvcHtmlString TextBox(this HtmlHelper htmlHelper, string name) {
TagBuilder tagBuilder = new TagBuilder("input");
tagBuilder.Attributes.Add("type", "text");
tagBuilder.Attributes.Add("name", name);
tagBuilder.Attributes.Add("crazy-override", "true");
return new MvcHtmlString(tagBuilder.ToString(TagRenderMode.Normal));
}
}
}
Обратите внимание, что пространство имен должно быть "ASP".