Создание делегатов вручную или использование делегатов Action / Func

Сегодня я думал об объявлении вот этого:

private delegate double ChangeListAction(string param1, int number);

но почему бы не использовать это:

private Func<string, int, double> ChangeListAction;

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

private Action<string,int> ChangeListAction;

так где же преимущество в объявлении делегата с ключевым словом delegate?

Это из-за .NET 1.1, и с .NET 2.0 пришло Action<T>, а с .NET 3.5 пришло Func<T>?


person Elisabeth    schedule 19.12.2010    source источник


Ответы (8)


Преимущество - ясность. Если дать типу явное имя, читателю будет более понятно, что он делает.

Это также поможет вам при написании кода. Такая ошибка:

cannot convert from Func<string, int, double> to Func<string, int, int, double>

менее полезен, чем тот, в котором говорится:

cannot convert from CreateListAction to UpdateListAction

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

person Mark Byers    schedule 19.12.2010
comment
Func ‹T1, T2, U› имеет имя вроде Func ‹T1, T2, T3, U›, поэтому, если компилятор не скажет: «Невозможно преобразовать из Func1Name в Func2Name, это НЕ будет менее полезным». - person Elisabeth; 19.12.2010

С появлением семейств делегатов Action и Func пользовательские делегаты стали менее использоваться, но последние по-прежнему находят применение. К преимуществам настраиваемых делегатов относятся:

  1. Как указывали другие, явно выражает намерение, в отличие от общих Action и Func (Patrik имеет очень хорошее замечание о значимых именах параметров).

  2. Вы можете указать _5 _ / _ 6_ параметров в отличие от двух других универсальных делегатов. Например, вы можете иметь

    public delegate double ChangeListAction(out string p1, ref int p2);
    

    но нет

    Func<out string, ref int, double> ChangeListAction;
    
  3. Кроме того, с настраиваемыми делегатами вам нужно написать ChangeListAction (я имею в виду определение) только один раз где-то в базе кода, тогда как, если вы не определите его, вам придется мусорить везде Func<string, int, double> повсюду. В последнем случае поменять подпись будет хлопотно - плохой случай не высохнуть.

  4. Могут иметь необязательные параметры.

    public delegate double ChangeListAction(string p1 = "haha", int p2);
    

    но нет

    Func<string, int, double> ChangeListAction = (p1 = "haha", p2) => (double)p2; 
    
  5. Вы можете использовать ключевое слово params для параметров метода, но не Action/Func.

    public delegate double ChangeListAction(int p1, params string[] p2);
    

    но нет

    Func<int, params string[], double> ChangeListAction;
    
  6. Что ж, если вам действительно не повезло и вам нужны параметры больше 16 (пока что) :)


Что касается достоинств Action и Func:

  1. Это быстро и грязно, и я использую его повсюду. Это делает код коротким, если вариант использования тривиален (настраиваемые делегаты у меня вышли из моды).

  2. Что еще более важно, его тип совместим с разными доменами. Action и Func определены структурой, и они работают без сбоев, пока типы параметров совпадают. Вы не можете получить ChangeSomeAction за ChangeListAction. Linq находит большое применение в этом аспекте.

person nawfal    schedule 15.05.2013
comment
Вот еще один, где вы не можете использовать Func - вы не можете использовать Func, если он должен возвращать себя, как указано здесь: stackoverflow.com/questions/27989296 / - person Marwie; 22.01.2015
comment
спасибо @Marwie. Полезный. В какой-то момент добавлю к своему ответу. - person nawfal; 23.01.2015
comment
В примере № 5 params должен быть последним параметром. - person Jalal; 07.05.2016
comment
@Jalal, ты прав. Глупая ошибка. Кто угодно может редактировать ответы в любое время :) - person nawfal; 09.05.2016
comment
Вы упомянули об этом с помощью LINQ. Я считаю, что Action / Func являются основой анонимных (закрывающих) методов. - person Todd; 06.07.2018
comment
@Todd Я тебя не понимаю. Мой ответ не о LINQ. - person nawfal; 06.07.2018
comment
Извините, я слишком старался, чтобы мой комментарий был кратким. Вы ссылаетесь на очень важную причину, по которой Action / Func вообще существуют. Причина WHY во многом способствует пониманию различий между Action / Func и делегатом. Вы сказали Linq finds great use of this aspect.. Выделение: Action и Func работают без проблем во всех доменах. Я говорю, что есть еще кое-что: Action / Func были в основном созданы для анонимных (закрывающих) методов (и выражений), которые позволяют LINQ и Fluent-подобные интерфейсы кодирования в C #. - person Todd; 06.07.2018
comment
@Todd Наличие встроенных делегатов (например, Func) было необходимостью для работы LINQ (как для объектов, так и для объектов) и, как правило, полезно иметь встроенные типы, тогда как замыкания существовали в C # еще до .NET. 3.5. Action / Func не нужны для написания анонимных методов на C #, и это единственный тип делегата, который может быть скомпилирован с использованием деревьев выражений. Благодаря встроенным типам в .NET все стало проще. И нет, я не намекаю, что Action / Func существуют по причине LINQ, я просто не знаю. - person nawfal; 07.07.2018
comment
Опять же, я просто пытаюсь помочь читателям, но мы, кажется, опускаемся до бесплодных споров. Я должен был быть конкретным и описать их как лямбда-выражения, а не анонимные функции: delegate(string s) { Console.WriteLine(s); } было возможно в C # 2.0. Когда я говорил об анонимных (закрывающих) методах, я имел в виду функцию языка C #, а не реализацию IL и ее поддержку. Я уверен, что раньше можно было скомпилировать анонимные функции C # с делегатами. Лямбда-функции и LINQ были представлены в C # версии 3. - person Todd; 08.07.2018
comment
@Todd Fruitless, возможно, но я тоже хочу прояснить, что Action / Func не являются основой лямбда-выражений. Последний может использоваться с любым типом делегата. Action / Func просто сделали анонимные методы более доступными / простыми в использовании. - person nawfal; 09.07.2018
comment
LINQ был выпущен вместе с C # версии 3. Связь объективно есть. Не используется синтаксис delegate(type param) { body }. В языке C # они рассматриваются как Action ‹› / Func ‹›. Это очень важный момент. Пожалуйста, приведите пример, в котором я могу ошибаться. Action/Func just made anonymous methods more accessible/easier to use - Я не согласен, синтаксис Async / Func встроен в язык C #, до C # версии 3 этого синтаксиса не существовало. Хотя возможно, что мог использоваться синтаксис делегата anon, это не так. Я описываю то, что есть на самом деле, а не то, что могло бы быть. - person Todd; 09.07.2018
comment
@Todd Не используется делегат (параметр типа) {body} - нет объективной связи, поскольку new List<string>().Any(delegate (string param) { return true; }) также является допустимым синтаксисом. - person nawfal; 09.07.2018
comment
Синтаксис Async / Func встроен в язык C #. Я думаю, вы ошибаетесь в связи между типом делегата, например Func like, и синтаксисом для выражения анонимной функции. Нет, Func синтаксис не встроен в язык. В C # нет синтаксиса для Func. Func не представляет собой ничего особенного, это просто еще один тип, объявленный в BCL, для своего рода включения структурной типизации (для функций). Вы можете написать как Func<int, string> d = delegate(int param) { return ""; };, так и MyCustomDelegate = param => ""; Или вы используете неправильную терминологию для передачи. - person nawfal; 09.07.2018
comment
Я придерживаюсь своей исходной точки: Action / Func не являются ни основами лямбда-выражений, ни анонимных методов. Слово «основа» имеет сильный оттенок. Я бы сказал, что Action / Func были хорошими помощниками, просто сделав все закрытие чертовски проще. - person nawfal; 09.07.2018
comment
1) Когда вы видите delegate (string param) { return true; }) в обычной документации LINQ? Они используют (param) => { return true; }, который является Func ‹int, bool› или (param) => true, который также является Func ‹int, bool›. 2) Нет, это C #, основанный на BCL и IL: (param) => {return true;} - это синтаксис C #, который компилируется в Func ‹int, bool›, который наследуется от делегата. 3) ни основы лямбда - я имел в виду LINQ, а не более широкую возможность выражения лямбда. Оба они написаны на одном языке, и в примерах LINQ используется структура () => {}, а не delgate. Следовательно: соотнесены. - person Todd; 09.07.2018
comment
Позвольте нам продолжить это обсуждение в чате. - person Todd; 09.07.2018

Явное объявление делегата может помочь с некоторыми проверками типов. Компилятор может убедиться, что делегат, назначенный переменной, предназначен для использования в качестве ChangeListAction, а не как какое-то случайное действие, которое оказывается совместимым с подписью.

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

Вы должны использовать делегаты Func, Predicate и Action при разработке библиотеки общего назначения, такой как LINQ. В этом случае делегаты не имеют предопределенной семантики, кроме того факта, что они будут выполняться и действовать или использоваться в качестве предиката.

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

person Stilgar    schedule 19.12.2010

Поскольку в некоторых ответах упоминается, что победа - это ясность, вы называете тип, и поэтому пользователю вашего api будет легче понять. Я бы сказал - в большинстве случаев - объявляйте типы делегатов для ваших общедоступных API, но вполне нормально использовать Func<?,?> для внутренних целей.

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

person Patrik Hägne    schedule 19.12.2010

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

public delegate bool WndEnumProc(IntPtr hwnd, IntPtr lParam);
[DllImport("User32.dll")]
public static extern bool EnumWindows(WndEnumProc lpEnumFunc, IntPtr lParam);

Использование Func / Action просто не работает: 'Namespace.Class.WndEnumProc' is a 'field' but is used like a 'type':

public Func<IntPtr, IntPtr, bool> WndEnumProc;
[DllImport("User32.dll")]
public static extern bool EnumWindows(WndEnumProc lpEnumFunc, IntPtr lParam);

Следующий код компилируется, но выдает исключение при запуске, потому что System.Runtime.InteropServices.DllImportAttribute не поддерживает маршалинг универсальных типов:

[DllImport("User32.dll")]
public static extern bool EnumWindows(Func<IntPtr, IntPtr, bool> lpEnumFunc, IntPtr lParam);

Я представляю этот пример, чтобы показать всем, что: иногда делегат - ваш единственный выбор. И это разумный ответ на ваш вопрос why not use Action<T>/Func<T> ?

person Tyler Long    schedule 05.12.2014

Объявите делегат явно, когда вы начнете получать слишком много параметров в Func / Action, иначе вам придется оглядываться назад: «Что снова означает второй int?»

person Ana Betts    schedule 19.12.2010

Чтобы получить лучший и более подробный ответ, посмотрите @nawfal. Я постараюсь быть более упрощенным.

Вы объявляете член класса, поэтому вам следует придерживаться делегата. Использование delegate более наглядно и структурно.

Action/Func типы предназначены для передачи, поэтому вы должны использовать их больше как параметры и локальные переменные.

И на самом деле оба они наследуют Delegate класс. Action и Func являются универсальными типами и упрощают создание делегатов с разными типами параметров. А ключевое слово delegate фактически создает целый новый класс, унаследованный от Delegate в одном объявлении.

person Bizniztime    schedule 20.10.2016

Как сказано в MSDN, Func<> сам по себе предопределенный Delegate. Впервые я запуталась в этом. После эксперимента мое понимание стало более ясным. Обычно в C # мы видим

Type как указатель на Instance.

Та же концепция применяется к

Delegate как указатель на Method

Разница между этими вещами в том, что Delegate не владеют концепцией ООП, например, Inheritance. Чтобы прояснить ситуацию, я провел эксперимент с

public delegate string CustomDelegate(string a);

// Func<> is a delegate itself, BUILD-IN delegate
//==========
// Short Version Anonymous Function
Func<string, string> fShort = a => "ttt";
//----------
// Long Version Anonymous Function
Func<string, string> fLong = delegate(string a)
{
  return "ttt";
};
//----------
MyDelegate customDlg;
Func<string, string> fAssign;
// if we do the thing like this we get the compilation error!!
// because fAssign is not the same KIND as customDlg
//fAssign = customDlg;

Многие встроенные методы в фреймворке (например, LINQ) получают параметр делегата Func<>. Что мы можем сделать с помощью этого метода, так это

Declare делегат типа Func<> и передать его функции, а не Define пользовательскому делегату.

Например, из приведенного выше кода я добавляю еще код

string[] strList = { "abc", "abcd", "abcdef" };
strList.Select(fAssign); // is valid
//strList.Select(customDlg); // Compilation Error!!
person Pranithan T.    schedule 18.08.2016