Какие преимущества методов расширения вы нашли?

"Неверующий" из С# спрашивал меня, какова цель методов расширения. Я объяснил, что вы могли бы добавить новые методы для объектов, которые уже были определены, особенно если вы не являетесь владельцем/управляете источником исходным объектом.

Он поднял "Почему бы просто не добавить метод к своему классу?" Мы шли кругом (в хорошем смысле). Мой общий ответ заключается в том, что это еще один инструмент в наборе инструментов, и его ответ - бесполезная трата инструмента... но я думал, что получу более "просвещенный" ответ.

Каковы некоторые сценарии, которые вы использовали методы расширения, которые у вас не могли (или не должны были) использовать метод, добавленный в ваш собственный класс?

Ответ 1

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

У меня есть поставщик формата формат файла. Чтобы использовать его, мне нужно написать:

Console.WriteLine(String.Format(new FileSizeFormatProvider(), "{0:fs}", fileSize));

Создание метода расширения, который я могу написать:

Console.WriteLine(fileSize.ToFileSize());

Более чистый и простой.

Ответ 2

Преимущество методов расширения только - читаемость кода. Что это.

Методы расширения позволяют сделать это:

foo.bar();

вместо этого:

Util.bar(foo);

Теперь в С# есть много вещей, которые похожи на это. Другими словами, в С# есть много функций, которые кажутся тривиальными и не имеют большой пользы сами по себе. Однако, как только вы начнете комбинировать эти функции вместе, вы начнете видеть что-то просто немного больше, чем сумма его частей. LINQ значительно выигрывает от методов расширения, поскольку запросы LINQ будут почти нечитаемы без них. LINQ будет возможен без методов расширения, но не практичен.

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

Ответ 3

Не забудьте оснастить! Когда вы добавляете метод расширения M по типу Foo, вы получаете "M" в списке Foo intellisense (при условии, что класс расширения находится в области). Это сделать "M" намного проще, чем MyClass.M(Foo,...).

В конце концов, это просто синтаксический сахар в других местах - статические методы, но, как покупка дома: "местоположение, местоположение, местоположение!" Если он висит на типе, люди найдут его!

Ответ 4

Еще два преимущества методов расширения, с которыми я столкнулся:

  • свободный интерфейс может быть инкапсулирован в статический класс методов расширения, тем самым достигается разделение проблем между основным классом и его текущими расширениями; я видел, что они достигают большей ремонтопригодности
  • методы расширения могут быть отключены от интерфейсов, что позволяет вам указать контракт (через интерфейс) и связанную с ним последовательность действий на основе интерфейса (посредством методов расширения), снова предлагая разделение проблем.

Ответ 5

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

  • Расширить функциональность сторонних объектов (будь то коммерческих или внутренних для моей компании, но управляемых отдельной группой), которые во многих случаях будут отмечены как sealed.
  • Создать функциональность по умолчанию для интерфейсов без необходимости использования абстрактного класса

Возьмем, к примеру, IEnumerable<T>. Хотя он богат методами расширения, мне было досадно, что он не реализовал общий метод ForEach. Итак, я сделал свой собственный:

public void ForEach<T>(this IEnumerable<T> enumerable, Action<T> action)
{
    foreach ( var o in enumerable )
    {
        action(o);
    }
}

Voila, все мои объекты IEnumerable<T>, независимо от типа реализации, и независимо от того, написал ли я это, или кто-то другой, теперь имеет метод ForEach, добавив в мой код соответствующий "using".

Ответ 6

Одной из главных причин использования методов расширения является LINQ. Без методов расширения многое из того, что вы можете сделать в LINQ, было бы очень сложно. Методы Where(), Contains(), Select extension означают, что к существующим типам добавляется гораздо больше функциональных возможностей без изменения их структуры.

Ответ 7

Существует много ответов о преимуществах методов расширений; как насчет адресации недостатков?

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

Предположим, вы создали метод расширения, применяемый к определенному классу. Затем позже кто-то создает метод с одинаковой сигнатурой на этом самом классе.

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

Ответ 8

Свободные интерфейсы и контекстная чувствительность, продемонстрированные Грегом Янгом на CodeBetter

Ответ 9

Мой личный аргумент в отношении методов расширения - они хорошо вписываются в дизайн ООП: рассмотрим простой метод

bool empty = String.IsNullOrEmpty (myString)

по сравнению с

bool empty = myString.IsNullOrEmpty ();

Ответ 10

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

Мой короткий ответ - они почти устраняют необходимость в заводах.

Я просто укажу, что они не являются новой концепцией, и одна из самых важных их утверждений состоит в том, что они являются функцией убийцы в Objective-C (категориях). Они добавляют настолько большую гибкость для разработки на основе рамок, что NeXT в качестве основных пользователей использовали NSA и Wall Street финансовые моделеры.

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

Ответ 11

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


В качестве примера возьмем этот запрос LINQ:

numbers.Where(x => x > 0).Select(x => -x)

Оба Where и Select - это методы расширения, определенные в статическом классе Enumerable. Таким образом, если методы расширения не существовали, и это были обычные статические методы, последняя строка кода должна была бы выглядеть примерно так:

Enumerable.Select(Enumerable.Where(numbers, x => x > 0), x => -x)

Посмотрите, насколько более неприятен этот запрос.


Во-вторых, если вы хотите представить свой собственный оператор запроса, вы, естественно, не сможете определить его внутри статического класса Enumerable, как и все остальные стандартные операторы запросов, поскольку Enumerable находится в структуре и у вас нет контроля над этим классом. Поэтому вам нужно будет определить свой собственный статический класс, содержащий методы расширения. Затем вы можете получить такие запросы, как этот:

Enumerable.Select(MyEnumerableExtensions.RemoveNegativeNumbers(numbers), x => -x)
//                ^^^^^^^^^^^^^^^^^^^^^^
//                different class name that has zero informational value
//                and, as with 'Enumerable.xxxxxx', only obstructs the
//                query actual meaning.

Ответ 12

Справедливо, что вы можете добавить свой метод (extension) непосредственно в ваш класс. Но не все классы написаны вами. Классы из основной библиотеки или сторонних библиотек часто закрываются, и невозможно получить синтаксический сахар без методов расширения. Но помните, что методы расширения аналогичны (статическим) автономным методам, например. С++

Ответ 13

Методы расширения также могут помочь очистить ваши классы и зависимости классов. Например, вам может понадобиться метод Bar() для класса Foo, где используется Foo. Однако вам может понадобиться метод .ToXml() в другой сборке и только для этой сборки. В этом случае вы можете добавить необходимые зависимости System.Xml и/или System.Xml.Linq в этой сборке, а не в исходной сборке.

Преимущества: зависимости в вашей сборке определяемого класса сводятся только к основным потребностям, а другим потребляющим узлам будет запрещено использовать метод ToXml(). Подробнее см. эту презентацию PDC.

Ответ 14

Методы расширения - это действительно интеграция .NET "Внедрить иностранный метод" рефактору из книги Мартина Фаулера (вплоть до подписи метода), Они приходят в основном с теми же преимуществами и ловушками. В разделе, посвященном этому рефактору, он говорит, что они работают, когда вы не можете модифицировать класс, который должен действительно иметь этот метод.

Ответ 15

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

В сообществе С++ часто считается хорошей практикой ООП, которая предпочитает бесплатные функции без членов над членами, поскольку эти функции не нарушают инкапсуляцию, получая доступ к тем частным членам, которые им не нужны. Методы расширения, похоже, являются обходным путем для достижения того же. То есть, более чистый синтаксис для статических функций, которые не имеют доступа к закрытым членам.

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

Ответ 16

  • Intellisense для самого объекта, а не для вызова некоторой уродливой функции утилиты
  • Для функций преобразования можно изменить "XToY (X x)" на "ToY (этот X x)", что приводит к довольному x.ToY() вместо уродливого XToY (x).
  • Расширять классы, в которых вы не контролируете
  • Расширять функциональность классов, когда нежелательно добавлять методы к самим классам. Например, вы можете сохранить бизнес-объекты простыми и не требующими логики и добавить определенную бизнес-логику с уродливыми зависимостями в методах расширения.

Ответ 17

Я использую их для повторного использования классов объектной модели. У меня есть группа классов, которые представляют объекты, которые у меня есть в базе данных. Эти классы используются на стороне клиента только для отображения объектов, поэтому основное использование доступа к свойствам.

public class Stock {
   public Code { get; private set; }
   public Name { get; private set; }
}

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

public static class StockExtender {
    public static List <Quote> GetQuotesByDate(this Stock s, DateTime date)
    {...}
}

Таким образом, я могу использовать те же классы для обработки бизнес-логики и отображения пользовательского интерфейса, не перегружая клиентскую сторону ненужным кодом.

Интересно, что мои классы объектных моделей динамически генерируются с помощью Mono.Cecil, поэтому было бы очень сложно добавьте методы бизнес-логики, даже если бы я хотел. У меня есть компилятор, который читает файлы определения XML и генерирует эти классы заглушек, представляющие некоторый объект, который у меня есть в базе данных. Единственный подход в этом случае - расширить их.

Ответ 18

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

ИМО с использованием методов расширения для добавления поведения к вашим классам может быть:

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

Антипаттерн: Вы решили добавить поведение к типам в своей структуре с помощью методов расширения, а затем отправить их кому-то, кто в модульном тестировании. Теперь он застрял с каркасом, содержащим множество методов, которые он не может подделать.

Ответ 19

Это позволяет С# лучше поддерживать динамические языки, LINQ и еще десяток других вещей. Проверьте статья Скотта Гатри.

Ответ 20

В моем последнем проекте я использовал метод расширения для присоединения методов Validate() к бизнес-объектам. Я оправдывал это, потому что бизнес-объекты, где сериализуемые объекты передачи данных и будут использоваться в разных доменах, как они, где общие объекты электронной торговли, такие как продукт, клиент, продавец и т.д. В разных доменах бизнес-правила могут быть разными, поэтому я инкапсулировал мои поздняя связанная логика проверки в методе Validate, включенная в базовый класс объектов передачи данных. Надеюсь, это имеет смысл:)

Ответ 21

Один случай, когда методы расширения были весьма полезными, был в клиентском приложении, использующем веб-службы ASMX. Из-за сериализации возвращаемые типы веб-методов не содержат никаких методов (на клиенте доступны только общедоступные свойства этих типов).

Методы расширения позволяют использовать функциональность (на стороне клиента) для типов, возвращаемых веб-методами, без необходимости создавать еще одну объектную модель или многочисленные классы-оболочки на стороне клиента.

Ответ 22

Также помните, что методы расширения были добавлены как способ помочь Linq-запросу быть более читабельными при использовании в их стиле С#.

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

int n1 = new List<int> {1,2,3}.Where(i => i % 2 != 0).Last();

int n2 = Enumerable.Last(Enumerable.Where(new List<int> {1,2,3}, i => i % 2 != 0));

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

int n1 = new List<int> {1,2,3}.Where<int>(i => i % 2 != 0).Last<int>();

int n2 = Enumerable.Last<int>(Enumerable.Where<int>(new List<int> {1,2,3}, i => i % 2 != 0));

Случайно, параметры типа Where и Last не должны быть явно указаны, поскольку они могут быть выведены из-за наличия первого параметра этих двух методов (параметр, который вводится ключевое слово this и сделать их методы расширения).

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

В частности, это более элегантный и убедительный способ, которым я нашел метод базового класса, вызываемый любым подклассом и возвращающий строго типизированную ссылку на этот подкласс (с типом подкласса).

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

public abstract class Animal
{
    //some code common to all animals
}

public static class AnimalExtension
{
    public static TAnimal OpenTheEyes<TAnimal>(this TAnimal animal) where TAnimal : Animal
    {
        //Some code to flutter one eyelashes and then open wide
        return animal; //returning a self reference to allow method chaining
    }
}

public class Dog : Animal
{
    public void Bark() { /* ... */ }
}

public class Duck : Animal
{
    public void Kwak() { /* ... */ }
}

class Program
{
    static void Main(string[] args)
    {
        Dog Goofy = new Dog();
        Duck Donald = new Duck();
        Goofy.OpenTheEyes().Bark(); //*1
        Donald.OpenTheEyes().Kwak(); //*2
    }
}

Концептуально OpenTheEyes должен быть методом Animal, но затем должен возвращать экземпляр абстрактного класса Animal, который не знает конкретные методы подкласса, такие как Bark или Duck или что-то еще. 2 строки, прокомментированные как * 1 и * 2, затем повысят ошибку компиляции.

Но благодаря методам расширения мы можем иметь своего рода "базовый метод, который знает тип подкласса, на котором он называется".

Обратите внимание, что простой общий метод мог выполнить задание, но гораздо более неудобно:

public abstract class Animal
{
    //some code common to all animals

    public TAnimal OpenTheEyes<TAnimal>() where TAnimal : Animal
    {
        //Some code to flutter one eyelashes and then open wide
        return (TAnimal)this; //returning a self reference to allow method chaining
    }
}

На этот раз никаких параметров и, следовательно, невозможного вывода типа возврата. Вызов может быть не чем иным, как:

Goofy.OpenTheEyes<Dog>().Bark();
Donald.OpenTheEyes<Duck>().Kwak();

... который может весить код много, если задействовано больше цепочки (особенно, зная, что параметр типа всегда будет <Dog> на линии Goofy и <Duck> на Donald one...)

Ответ 23

У меня есть только одно слово, чтобы рассказать об этом: СОВЕРШЕНСТВОВАНИЕ это ключ для использования методов расширения

Ответ 24

Я думаю, что методы расширения помогают писать более понятный код.

Вместо того, чтобы вводить новый метод внутри вашего класса, как предложил ваш друг, вы помещаете его в пространство имен ExtensionMethods. Таким образом, вы поддерживаете логическое чувство порядка для своего класса. Методы, которые действительно не имеют прямого отношения к вашему классу, не будут загромождать его.

Я чувствую, что методы расширения делают ваш код более четким и более соответствующим образом организованным.

Ответ 25

Это позволяет вашему редактору /IDE делать автоматическое заполнение предложения умным.

Ответ 26

Я люблю их для создания html. Часто существуют разделы, которые используются повторно или генерируются рекурсивно, когда функция полезна, но в противном случае нарушает поток программы.

        HTML_Out.Append("<ul>");
        foreach (var i in items)
            if (i.Description != "")
            {
                HTML_Out.Append("<li>")
                    .AppendAnchor(new string[]{ urlRoot, i.Description_Norm }, i.Description)
                    .Append("<div>")
                    .AppendImage(iconDir, i.Icon, i.Description)
                    .Append(i.Categories.ToHTML(i.Description_Norm, urlRoot)).Append("</div></li>");
            }

        return HTML_Out.Append("</ul>").ToString();

Существуют также ситуации, когда объект нуждается в специальной логике, которая должна быть подготовлена ​​для HTML-методов расширения вывода, позволяет добавить эту функциональность, не смешивая презентацию и логику внутри класса.

Ответ 27

Я нашел методы расширения полезными для сопоставления вложенных общих аргументов.

Это звучит немного странно - но скажем, что у нас есть общий класс MyGenericClass<TList>, и мы знаем, что сам TList является общим (например, List<T>), я не думаю, что есть способ выкопать этот вложенный 'T' из списка без методов расширения или статических вспомогательных методов. Если у нас есть только статические вспомогательные методы, они (а) уродливы и (б) заставят нас перемещать функциональные возможности, принадлежащие классу, во внешнее местоположение.

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

public class Tuple { }
public class Tuple<T0> : Tuple { }
public class Tuple<T0, T1> : Tuple<T0> { }

public class Caller<TTuple> where TTuple : Tuple { /* ... */ }

public static class CallerExtensions
{
     public static void Call<T0>(this Caller<Tuple<T0>> caller, T0 p0) { /* ... */ }

     public static void Call<T0, T1>(this Caller<Tuple<T0, T1>> caller, T0 p0, T1 p1) { /* ... */ }
}

new Caller<Tuple<int>>().Call(10);
new Caller<Tuple<string, int>>().Call("Hello", 10);

Тем не менее, я не уверен, где должна быть разделительная линия - когда должен быть метод расширения, и когда это должен быть статический вспомогательный метод? Любые мысли?

Ответ 28

Методы расширения можно использовать для создания вида mixin на С#.

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

Это также можно использовать для включения ролей в С#, концепции, центральной для архитектуры DCI.

Ответ 29

У меня есть зоны ввода на моем экране, и все они должны выполнять стандартное поведение независимо от их точных типов (текстовые поля, флажки и т.д.). Они не могут наследовать общий базовый класс, поскольку каждый тип входной зоны уже происходит из определенного класса (TextInputBox и т.д.).

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

С помощью метода расширения я могу:

1) расширить класс WebControl, а затем получить унифицированное стандартное поведение во всех моих классах ввода

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

Ответ 30

Существует так много замечательных примеров методов расширения, особенно в IEnumerables, как указано выше.

например. если у меня есть IEnumerable<myObject>, я могу создать и расширить метод IEnumerable<myObject>

mylist List<myObject>;

... создать список

mylist.DisplayInMyWay();

Без методов расширения необходимо вызвать:

myDisplayMethod(myOldArray); // can create more nexted brackets.

Еще один замечательный пример - создание кругового связанного списка в мгновение ока!

Я могу "похвастаться"!

связанный со ссылками список с использованием методов расширения

Теперь объедините их и используя код метода расширения, читайте следующим образом.

myNode.NextOrFirst().DisplayInMyWay();

а не

DisplayInMyWay(NextOrFirst(myNode)).

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

myNode.Next.DoSomething()

Покажите это своему коллегу!:)