Как переопределить существующий метод расширения


Я хочу заменить методы расширения, включенные в .NET или ASP MVC framework, моими собственными методами.

пример

public static string TextBox(this HtmlHelper htmlHelper, string name)
{
   ...
}

это возможно? Я не могу использовать переопределение или новое ключевое слово.

5 55

5 ответов:

обновление: этот вопрос был тема моего блога в декабре 2013 года. Спасибо за отличный вопрос!


вы можете сделать это, в каком-то смысле. Но я должен начать с краткого обсуждения основного принципа проектирования разрешения перегрузки в C#. Все разрешение перегрузки, конечно же, связано с использованием набора методов с тем же именем и выбором из этого набора уникального лучшего члена для вызова.

есть много факторов, участвующих в определение того, какой метод является" лучшим"; разные языки используют различную" смесь " факторов, чтобы понять это. C#, в частности, сильно взвешивает "близость" данного метода к сайту вызова. Если задан выбор между применимым методом в базовом классе или новым применимым методом в производном классе, C# берет тот, который находится в производном классе, потому что он ближе, даже если тот, который находится в базовом классе, во всех других отношениях лучше соответствует.

и так мы бежим вниз по списку. Полученный классы ближе, чем базовые классы. Внутренние классы ближе, чем внешние классы. Методов в иерархии классов ближе, чем методы расширения.

а теперь перейдем к вашему вопросу. Близость метода расширения зависит от (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 получить первый идти, то классы в FrobCo.MyExtensionNamespace, затем в FrobCo, а потом занятия в BazCo.TheirExtensionNamespace.

это понятно?

методы расширения нельзя переопределить, поскольку они не являются методами экземпляра и не являются виртуальными.

компилятор будет жаловаться, если вы импортируете оба класса метода расширения через пространство имен, поскольку он не будет знать, какой метод вызывать:

вызов неоднозначен между следующих методов или свойств: ...

единственный способ обойти это, чтобы вызвать метод расширения, используя обычный синтаксис статического метода. Так что вместо это:

a.Foo();

вы должны сделать это:

YourExtensionMethodClass.Foo(a);

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

но Мэтт Манела говорит о том, как методы экземпляра имеют приоритет над методами расширения: 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/

Edit: я забыл о проблеме неоднозначности, поэтому лучше всего попробовать не включать методы расширения, которые вы хотите заменить. Таким образом, вам может потребоваться не использовать директиву "using", а просто ввести полное имя пакета некоторых классов, и это может решить проблему.

основываясь на предпосылке Eric (и тот факт, что код представления отображается в пространстве имен 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".

В дополнение к существующим ответами, вы также можете "переопределить" методы расширения, изменив сигнатуру метода:

1.) Используйте более конкретный тип

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

// "override" the default Linq method by using a more specific type
public static bool Any<TSource>(this List<TSource> source)
{
    return Enumerable.Any(source);
}

// this will call YOUR extension method
new List<T>().Any();

2.) Добавить фиктивный параметр

не так красива. Кроме того, это будет требуется изменить существующие вызовы, чтобы включить фиктивный аргумент.

// this will be called instead of the default Linq method when "override" is specified
public static bool Any<TSource>(this IEnumerable<TSource> source, bool @override = true)
{
    return source.Any();
}

// this will call YOUR extension method
new List<T>().Any(true);