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

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

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

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

 private Func ChangeListAction; 

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

 private Action ChangeListAction; 

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

Это из-за .NET 1.1, а с .NET 2.0 появился Action а с .NET 3.5 появился Func ?

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

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

     cannot convert from Func to Func 

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

     cannot convert from CreateListAction to UpdateListAction 

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

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

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

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

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

      но нет

       Func ChangeListAction; 
    3. Кроме того, с пользовательскими делегатами вам нужно написать ChangeListAction (я имею в виду определение) только один раз в вашей базе кода, тогда как если вы его не определяете, вам придется помешать везде Func всему. Изменение подписи будет проблемой в последнем случае – плохой случай, когда вы не сушите.

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

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

      но нет

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

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

      но нет

       Func ChangeListAction; 
    6. Ну, если вам действительно не повезло и нужны параметры более 16 (на данный момент) 🙂


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

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

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

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

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

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

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

    В некоторых ответах упоминается, что выигрыш – это ясность, вы называете тип, и поэтому пользователю будет легче понять пользователя вашего api. Я бы сказал – в большинстве случаев – объявлять типы делегатов для вашей публичной apis, но вполне нормально использовать 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 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 lpEnumFunc, IntPtr lParam); 

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

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

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

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

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

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

    Как сказал 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 fShort = delegate(string a) { return "ttt"; }; // Long Version Anonymous Function //---------- Func fLong = a => "ttt"; MyDelegate customDlg; Func 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!! 
    Давайте будем гением компьютера.