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

Ответ 1

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

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

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

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

cannot convert from CreateListAction to UpdateListAction

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

Ответ 2

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

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

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

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

    но не

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

  • Может иметь необязательные параметры.

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

    но не

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

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

    но не

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


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

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

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

Ответ 3

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

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

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

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

Ответ 4

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

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

Ответ 5

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

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

Ответ 6

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

Ответ 7

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

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

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

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

Ответ 8

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

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<> delegate. Мы можем сделать это с помощью

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

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

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