Когда нарушаются методы расширения?
В настоящее время мы обсуждаем, являются ли методы расширения в .NET плохими или нет. Или при каких обстоятельствах методы расширения могут вводить трудно найти ошибки или каким-либо другим образом ведут себя неожиданно.
Мы придумали:
- Написание метода расширения для типов, которые не находятся под вашим контролем (например, расширение DirectoryInfo с помощью GetTotalSize() и т.д.), является плохим, поскольку владелец API может ввести метод, который скрывает наше расширение - и может иметь разные граничные случаи. Например, тестирование для null в методе расширения автоматически преобразуется в исключение NullReferenceException, если метод расширения больше не используется из-за скрытия.
Вопрос:
- Есть ли какие-либо другие опасные ситуации, кроме "скрытия", о которых мы не думаем?
Edit:
Другая очень опасная ситуация.
Предположим, что у вас есть метод расширения:
namespace Example.ExtensionMethods
{
public static class Extension
{
public static int Conflict(this TestMe obj)
{
return -1;
}
}
}
И используйте его:
namespace Example.ExtensionMethods.Conflict.Test
{
[TestFixture]
public class ConflictExtensionTest
{
[Test]
public void ConflictTest()
{
TestMe me = new TestMe();
int result = me.Conflict();
Assert.That(result, Is.EqualTo(-1));
}
}
}
Обратите внимание, что пространство имен, в котором вы его используете, длиннее.
Теперь вы ссылаетесь на dll с этим:
namespace Example.ExtensionMethods.Conflict
{
public static class ConflictExtension
{
public static int Conflict(this TestMe obj)
{
return 1;
}
}
}
И ваш тест не сработает! Он будет компилироваться без ошибки компилятора. Он будет просто терпеть неудачу. Без необходимости указывать "using Example.ExtensionMethods.Conflict". Компилятор будет проецировать имя пространства имен и найти Example.ExtensionMethods.Conflict.ConflictExtension перед Example.ExtensionMethods.Extension и будет использовать этот , не жалуясь на неоднозначные методы расширения. О, ужас!
Ответы
Ответ 1
Некоторые раритеты:
- методы расширения могут быть вызваны в экземплярах
null
; это может ввести в заблуждение (но иногда полезно).
- проблема с "скрытием" - это biggie, если у них разные намерения.
- В равной степени вы можете получить другой метод расширения с тем же именем из двух разных пространств имен; если у вас есть только одно из двух пространств имен, это может привести к непоследовательному поведению (в зависимости от того)...
- ... но если кто-то добавит аналогичный (такой же сигнатурный) метод расширения во втором пространстве имен, который использует ваш код, он сломается во время компиляции (двусмысленный)
(edit) И, конечно, есть бомба "Nullable<T>
/new()
" (см. здесь)...
Ответ 2
Я не согласен, весь смысл методов расширения заключается в том, чтобы добавить ваших членов в классы с черным ящиком. Как и все остальное, есть подводные камни, вы должны помнить об именовании, реализации и понимать порядок иерархии методов.
Ответ 3
Один обрыв, который мы только что нашли в проекте MoreLINQ: если вы пишете общий метод расширения, невозможно убедиться он будет работать со всеми типами. У нас есть метод с этой подписью:
public static IEnumerable<T> Concat<T>(this T head, IEnumerable<T> tail)
Вы не можете использовать это с помощью:
"foo".Concat(new [] { "tail" });
из-за метода string.Concat
...
Ответ 4
Я использовал Ruby on Rails почти до тех пор, пока я использовал С#. Ruby позволяет сделать что-то похожее на новые методы расширения. Конечно, есть потенциальные проблемы, если кто-то назвал метод одинаковым, но преимущества возможности добавления методов в закрытый класс намного перевешивают потенциальное несогласие (которое, вероятно, было бы вызвано плохим дизайном или плохим планированием).
Ответ 5
Одна вещь, которую вы можете сделать, чтобы убедиться, что методы расширения не конфликтуют с другими методами (расширение или иное), заключается в использовании FxCop с правила, такие как Предотвращать дублирование меток расширения..
Ответ 6
То, что .net вызывает методы расширения, также является ограниченной формой MonkeyPatching (попробуйте проигнорировать php rant там).
Это должно дать вам материал для обсуждения.
Ответ 7
Прежде всего, я считаю, что ваша формулировка немного вводит в заблуждение. Я так понимаю, вы говорите о "типах", а не о "объектах".
Во-вторых, большое преимущество методов расширения заключается в том, что вы можете добавлять функции к типу, который вы не контролируете. Если вы управляете типом, почему бы просто не изменить тип вместо того, чтобы полагаться на методы расширения?
Ответ 8
Мы приняли отношение в моей команде, что методы расширения настолько полезны, что вы не можете реально их запретить, но настолько опасны (главным образом из-за проблемы с укрытием), что вы должны быть немного осторожны. Поэтому мы решили, что все имена метода расширения должны иметь префикс с X
(поэтому у нас есть набор методов XInit...()
для инициализации элементов управления, например, полезными способами). Таким образом, а) вероятность столкновения имен уменьшается и b) программист знает, что использует метод расширения, а не метод класса.