Создание делегатов вручную и использование делегатов 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>?

8 ответов


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

Он также поможет вам, когда вы пишете код. Такая ошибка:

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

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

cannot convert from CreateListAction to UpdateListAction

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


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

  1. как указывали другие, передает намерение явно в отличие от generic Action и Func (Патрик имеет очень хороший момент о значимых именах параметров).

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


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

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

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

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


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

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


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

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


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


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

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

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

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


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

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

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

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

разница между этими вещами есть Delegate do не обладают понятием ООП, например,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;

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

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

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

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