больше преимуществ или недостатков делегировать членов по сравнению с классическими функциями? - PullRequest
14 голосов
/ 06 ноября 2011
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 является делегатом. Однако в этом контексте делегаты могут выполнять аналогичную роль.

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

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

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

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

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

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

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

Ответы [ 6 ]

18 голосов
/ 06 ноября 2011

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

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

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

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

Еще одно преимущество стратегии «методы - поля типа делегата» состоит в том, что вы можете реализовать методы совместного использования кода, отличные от тех, которые мы «запекли» в язык. Защищенное поле типа делегата - это, по сути, виртуальный метод , но более гибкий. Производные классы могут заменить его тем, что они хотят, и вы эмулировали обычный виртуальный метод. Но вы можете создать собственные механизмы наследования; например, если вам действительно нравится наследование прототипа, у вас может быть соглашение о том, что если поле имеет значение null, то вместо этого вызывается метод в некотором прототипном экземпляре и т. д.

Основным недостатком метода «методы являются полями типа делегата» является то, что, конечно, перегрузка больше не работает. Поля должны быть уникальными по имени; методы просто должны быть уникальными в подписи. Кроме того, вы не получаете универсальные поля так, как мы получаем универсальные методы, поэтому вывод типа метода перестает работать.

2 голосов
/ 06 ноября 2011

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

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

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

Производительность и надежность

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

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

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

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

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

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

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

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

Если вы действительно хотели пойти так далеко, чтобывсегда используйте делегаты для замены методов, вы должны использовать такой язык как 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);
    }
}
  • Я нашел конкретный случай, когдаЯ думаю, что публичные свойства делегатов гарантированы: для реализации Pattern Method Pattern с экземплярами вместо производных классов ...

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

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; }
}
2 голосов
/ 06 ноября 2011

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

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

1 голос
/ 27 января 2012

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

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 };
  }
}
1 голос
/ 06 ноября 2011

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

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

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

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

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

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

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

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

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

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

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

...