Создание делегатов вручную и использование делегатов 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
семейство делегатов сделало пользовательские делегаты менее используемыми, но последний все еще находит использование. Преимущества пользовательских делегатов включают:
как указывали другие, передает намерение явно в отличие от generic
Action
иFunc
(Патрик имеет очень хороший момент о значимых именах параметров).-
вы можете указать
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
находит большое использование этого аспекта.
объявление делегата явно может помочь с некоторыми проверками типа. Компилятор может убедиться, что делегат, назначенный переменной, предназначен для использования в качестве 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!!