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

Больше преимуществ или недостатков для делегирования участников по сравнению с классическими функциями?

class my_class
{
    public int add_1(int a, int b) {return a + b;}
    public func<int, int, int> add_2 = (a, b) => {return a + b;}
}

add_1 - это функция, тогда как add_2 является делегатом. Однако в этом контексте делегаты могут выполнять аналогичную роль.

Из-за прецедента и дизайна языка выбор по умолчанию для методов С# должен быть функциями.

Однако оба подхода имеют свои плюсы и минусы, поэтому я подготовил список. Есть ли еще какие-либо преимущества или недостатки для любого подхода?

Преимущества для обычных методов.

  • более обычный
  • внешние пользователи функции видят именованные параметры - для синтаксиса add_2 arg_n и типа обычно недостаточно информации.
  • работает лучше с intellisense - ty Minitech
  • работает с рефлексией - Minitech
  • работает с наследованием - Эрик Липперт
  • имеет "this" - ty CodeInChaos
  • более низкие накладные расходы, скорость и память - количество Minitech и CodeInChaos
  • не нужно думать о public\private в отношении как изменения, так и использования функции. - ty CodeInChaos
  • менее динамично, разрешено меньшее, что не известно во время компиляции - CodeInChaos

Преимущества методов "типа типа делегата".

  • более согласованные, а не функции-члены и члены данных, это всего лишь элементы данных.
  • может внешне выглядеть и вести себя как переменная.
  • Сохранение его в контейнере хорошо работает.
  • несколько классов могут использовать одну и ту же функцию, как если бы это была каждая из функций-членов, это было бы очень общим, кратким и имело бы хорошее повторное использование кода.
  • легко использовать в любом месте, например, в качестве локальной функции.
  • предположительно хорошо работает при передаче мусора.
  • более динамичное, меньшее время должно быть известно во время компиляции, например, могут быть функции, которые настраивают поведение объектов во время выполнения.
  • как бы инкапсулируя его код, можно комбинировать и перерабатывать, msdn.microsoft.com/en-us/library/ms173175%28v=vs.80%29.aspx
  • внешние пользователи функции видят неименованные параметры - иногда это полезно, хотя было бы неплохо назвать их.
  • может быть более компактным, в этом простом примере, например, возврат может быть удален, если есть один параметр, скобки также могут быть удалены.
  • roll you'r собственное поведение, подобное наследованию, - Эрик Липперт
  • другие соображения, такие как функциональные, модульные, распределенные, (написание кода, тестирование или рассуждение о коде) и т.д.

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

4b9b3361

Ответ 1

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

Для частных полей я использую этот шаблон довольно часто, обычно так:

class C
{
    private Func<int, int> ActualFunction = (int y)=>{ ... };
    private Func<int, int> Function = ActualFunction.Memoize();

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

Другим преимуществом стратегии "методы стратегии типа делегата" является то, что вы можете применять методы совместного использования кода, которые отличаются от тех, которые мы "испекли" на языке. Защищенное поле типа делегата по сути является виртуальным методом, но более гибким. Производные классы могут заменить его тем, что они хотят, и вы эмулируете обычный виртуальный метод. Но вы можете создавать механизмы пользовательского наследования; если вам действительно нравится наследование прототипа, например, вы можете иметь соглашение о том, что если поле имеет значение NULL, то вместо этого вызывается метод в некотором прототипном экземпляре и т.д.

Основным недостатком подхода методов-are-fields-of-delegate-type является то, что, конечно, перегрузка больше не работает. Поля должны быть уникальными по названию; методы просто должны быть уникальными в подписи. Кроме того, вы не получаете общие поля так, как мы получаем общие методы, поэтому вывод типа метода перестает работать.

Ответ 2

Второй, по моему мнению, абсолютно не имеет преимущества по сравнению с первым. Он гораздо менее читабельен, вероятно, менее эффективен (учитывая, что Invoke должен подразумеваться) и не является более кратким. Что еще, если вы когда-либо используете отражение, оно не будет отображаться как метод, поэтому, если вы сделаете это, чтобы заменить свои методы в каждом классе, вы можете сломать что-то похожее на то, что оно должно работать. В Visual Studio IntelliSense не будет включать описание метода, поскольку вы не можете помещать комментарии XML на делегатов (по крайней мере, не так, как вы бы поместили их на обычные методы), и вы не знаете, что они point anyway, если только он не читается (но что, если конструктор изменил его?), и он будет отображаться как поле, а не метод, который запутывает.

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

Ответ 3

Почему вам следует избегать делегатов как методов по умолчанию и какие альтернативы:

Кривая обучения

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

Перф и надежность

Там есть потеря производительности при вызове делегатов таким образом. Это еще одна причина, по которой я бы по умолчанию использовал объявление традиционного метода, если оно не включило что-то особенное в моем шаблоне.

Там также проблема безопасности выполнения. Открытые поля обнуляются. Если вы передали экземпляр класса с открытым полем, вам нужно будет проверить, что он не имеет значения null перед его использованием. Это болит перфекционизм и немного хромает.

Вы можете обойти это, изменив все общедоступные поля на свойства (как правило, во всех стандартах кодирования .Net). Затем в setter запустите ArgumentNullException, если кто-то попытается назначить null.

Дизайн программы

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

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

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

Альтернативы

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

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

Использование

... Пример кода:

public class Context
{
    private Func<int, int, int> executeStrategy;

    public Context(Func<int, int, int> executeStrategy) {
        this.executeStrategy = executeStrategy;
    }

    public int ExecuteStrategy(int a, int b) {
        return executeStrategy(a, b);
    }
}
  • Я нашел конкретный случай, когда я считаю, что свойства публичного делегирования защищены: для реализации Шаблон метода шаблона с экземплярами вместо производных классов...

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

var test = new MyFancyUITest
{
    // I usually name these things in a more test specific manner...
    Setup = () => { /* ... */ },
    TearDown = () => { /* ... */ },
};
test.Execute();

Поддержка Intellisense

внешние пользователи функции видят неименованные параметры - иногда это полезно, хотя было бы неплохо назвать их.

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

public class MyClass
{
    public delegate int DoSomethingImpl(int foo, int bizBar);
    public DoSomethingImpl DoSomething = (x, y) => { return x + y; }
}

Ответ 4

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

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

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

  • Если вы используете синтаксис инициализатора поля, вы не можете получить доступ к this. Поэтому синтаксис инициализатора поля в основном полезен для статических методов.

  • Делает статический анализ намного сложнее, поскольку реализация этого метода неизвестна во время компиляции.

В некоторых случаях могут быть полезны свойства/поля делегата:

  • Обработчики какого-то типа. Особенно, если мультилистинг (и, следовательно, шаблон подписки на события) не имеет особого смысла.
  • Назначение чего-то, что нелегко описать простым телом метода. Такие, как memoized функция.
  • Делегат генерируется во время выполнения или по крайней мере его значение определяется только во время выполнения

Ответ 5

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

class MyClassConventional {
  int? someValue; // When Mark() is called, remember the value so that we can do something with it in Process(). Not used in any other method.
  int X;

  void Mark() {
    someValue = X;
  }

  void Process() {
    // Do something with someValue.Value
  }
}

class MyClassClosure {
  int X;

  Action Process = null;

  void Mark() {
    int someValue = X;
    Process = () => { // Do something with someValue };
  }
}

Ответ 6

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

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

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

Единственное исключение из этого правила - если у вас есть потребность в одном из преимуществ, и эта потребность не может быть удовлетворена каким-либо другим способом. В этом случае вам нужно написать больше комментариев, чем код, чтобы объяснить, почему у вас есть все основания для этого. Будьте готовы ответить так же ясно, как и Эрик Липперт. Вы лучше сможете объяснить, а также Эрик делает то, что вы не можете выполнить свои требования и написать понятный код в то же время.