Создание делегатов вручную против использования делегатов Action / Func - PullRequest
64 голосов
/ 19 декабря 2010

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

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>?

Ответы [ 8 ]

66 голосов
/ 15 мая 2013

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

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

  2. Вы можете указать ref / out параметры в отличие от двух других общих делегатов. Например, вы можете иметь

    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 находит большое использование этого аспекта.

51 голосов
/ 19 декабря 2010

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

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

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

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

cannot convert from CreateListAction to UpdateListAction

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

11 голосов
/ 19 декабря 2010

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

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

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

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

7 голосов
/ 19 декабря 2010

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

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

6 голосов
/ 05 декабря 2014

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

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> ?

5 голосов
/ 19 декабря 2010

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

2 голосов
/ 20 октября 2016

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

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

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

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

0 голосов
/ 18 августа 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 = delegate(string a)
{
  return "ttt";
};
// Long Version Anonymous Function
//----------
Func<string, string> fLong = a => "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!!
...