Подтвердить что ты не робот

Когда нарушаются методы расширения?

В настоящее время мы обсуждаем, являются ли методы расширения в .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 и будет использовать этот , не жалуясь на неоднозначные методы расширения. О, ужас!

4b9b3361

Ответ 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 позволяет сделать что-то похожее на новые методы расширения. Конечно, есть потенциальные проблемы, если кто-то назвал метод одинаковым, но преимущества возможности добавления методов в закрытый класс намного перевешивают потенциальное несогласие (которое, вероятно, было бы вызвано плохим дизайном или плохим планированием).

Ответ 6

То, что .net вызывает методы расширения, также является ограниченной формой MonkeyPatching (попробуйте проигнорировать php rant там).

Это должно дать вам материал для обсуждения.

Ответ 7

Прежде всего, я считаю, что ваша формулировка немного вводит в заблуждение. Я так понимаю, вы говорите о "типах", а не о "объектах".

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

Ответ 8

Мы приняли отношение в моей команде, что методы расширения настолько полезны, что вы не можете реально их запретить, но настолько опасны (главным образом из-за проблемы с укрытием), что вы должны быть немного осторожны. Поэтому мы решили, что все имена метода расширения должны иметь префикс с X (поэтому у нас есть набор методов XInit...() для инициализации элементов управления, например, полезными способами). Таким образом, а) вероятность столкновения имен уменьшается и b) программист знает, что использует метод расширения, а не метод класса.