Crear delegates manualmente versus usar delegates Action / Func

Hoy estaba pensando en declarar esto:

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

pero por qué no usar esto:

 private Func ChangeListAction; 

o si ChangeListAction no tuviera valor de retorno que pudiera usar:

 private Action ChangeListAction; 

Entonces, ¿dónde está la ventaja al declarar un delegado con la palabra clave delegate ?

¿Es por .NET 1.1, y con .NET 2.0 llegó Action y con .NET 3.5 llegó Func ?

La ventaja es claridad. Al darle al tipo un nombre explícito, es más claro para el lector lo que hace.

También lo ayudará cuando escriba el código. Un error como este:

 cannot convert from Func to Func 

es menos útil que uno que dice:

 cannot convert from CreateListAction to UpdateListAction 

También significa que si tiene dos delegates diferentes, los cuales toman los mismos tipos de parámetros pero conceptualmente hacen dos cosas completamente diferentes, el comstackdor puede asegurarse de que no pueda usar accidentalmente uno queriendo decir el otro.

El advenimiento de la familia de delegates de Action y Func ha hecho que los delegates personalizados sean menos utilizados, pero este último aún encuentra usos. Las ventajas de los delegates personalizados incluyen:

  1. Como han señalado otros, transmite intenciones claramente a diferencia de Action y Func generics ( Patrik tiene un muy buen punto sobre nombres de parámetros significativos).

  2. Puede especificar parámetros de ref / out diferencia de los otros dos delegates generics. Por ejemplo, puede tener

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

    pero no

     Func ChangeListAction; 
  3. Además, con los delegates personalizados, debe escribir ChangeListAction (me refiero a la definición) solo una vez en algún lugar de su base de códigos, mientras que si no define uno, tendrá que Func basura en todas partes Func todas partes. Cambiar la firma será una molestia en este último caso, un mal caso de no estar seco.

  4. Puede tener parámetros opcionales

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

    pero no

     Func ChangeListAction = (p1 = "haha", p2) => (double)p2; 
  5. Puede tener la palabra clave params para los parámetros de un método, no así con Action/Func .

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

    pero no

     Func ChangeListAction; 
  6. Bueno, si estás realmente fuera de suerte y necesitas parámetros de más de 16 (por el momento) 🙂


En cuanto a los méritos de Action y Func :

  1. Es rápido y sucio, y lo uso por todas partes. Hace que el código sea corto si el caso de uso es trivial (los delegates personalizados han pasado de moda conmigo).

  2. Más importante aún, su tipo es compatible en todos los dominios. Action y Func son framework definidos, y funcionan perfectamente siempre que los tipos de parámetros coincidan. No puede tener ChangeSomeAction para ChangeListAction . Linq encuentra un gran uso de este aspecto.

Declarar explícitamente a un delegado puede ayudar con algunas comprobaciones de tipo. El comstackdor puede asegurarse de que el delegado asignado a la variable se use como ChangeListAction y no de alguna acción aleatoria que resulte ser compatible con la firma.

Sin embargo, el valor real de declarar a su propio delegado es que le da un significado semántico. Una persona que lee el código sabrá lo que el delegado está haciendo por su nombre. Imagínese si tuviera una clase con tres campos int pero, en su lugar, declaró una matriz de tres elementos int. La matriz puede hacer lo mismo pero los nombres de los campos brindan información semántica que es útil para los desarrolladores.

Debe usar delegates Func, Predicado y Acción cuando diseña una biblioteca de propósito general como LINQ. En este caso, los delegates no tienen una semántica predefinida que no sea el hecho de que ejecutarán y actuarán o se usarán como un predicado.

En una nota al margen hay un problema de equilibrio similar con Tuple vs tipo anónimo contra declarar su propia clase. Podrías simplemente pegar todo en una Tupla pero luego las propiedades son solo Item1, Item2 que no dice nada sobre el uso del tipo.

Como algunas respuestas mencionan que la victoria es clara, usted nombra el tipo y será más fácil de entender para un usuario de su API. Yo diría que, en la mayoría de los casos, declaro tipos de delegado para sus apis públicas, pero está bastante bien usar Func Internamente.

Un gran beneficio de declarar el tipo de delegado que no se menciona en las otras respuestas es que, aparte de dar un nombre al tipo, también se pueden nombrar los parámetros, esto boostá enormemente la usabilidad.

He encontrado un caso de uso especial donde solo puedes usar delegar:

 public delegate bool WndEnumProc(IntPtr hwnd, IntPtr lParam); [DllImport("User32.dll")] public static extern bool EnumWindows(WndEnumProc lpEnumFunc, IntPtr lParam); 

El uso de Func / Action simplemente no funciona: '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); 

El siguiente código se comstack, pero arroja una excepción cuando se ejecuta porque System.Runtime.InteropServices.DllImportAttribute no admite la recostackción de tipos generics:

 [DllImport("User32.dll")] public static extern bool EnumWindows(Func lpEnumFunc, IntPtr lParam); 

Presento este ejemplo para mostrarle a todos que: a veces delegar es su única opción. Y esta es una respuesta razonable a su pregunta, why not use Action/Func ?

Declare explícitamente al delegado cuando comience a obtener demasiados parámetros en Func / Action; de lo contrario, tendrá que mirar hacia atrás, “¿Qué significa nuevamente el segundo int?”

Para una respuesta mejor y más elaborada mira @nawfal. Trataré de ser más simplista.

Estás declarando un miembro de una clase, por lo que debes quedarte con el delegado. Usar delegate es más descriptivo y estructural.

Action/Func tipos Action/Func están hechos para pasar, por lo que debe usarlos más como parámetros y variables locales.

Y en realidad, ambos están heredando la clase Delegate . Action y Func son tipos generics y simplifican la creación de delegates con diferentes tipos de parámetros. Y la palabra clave delegar realmente crea una clase completamente nueva que hereda del Delegado en una statement.

Como dijo MSDN , Func<> es un Delegate predefinido. Por primera vez, me confundí acerca de esto. Después de lo experimental, mi comprensión fue bastante más clara. Normalmente, en C #, podemos ver

Type como un puntero a Instance .

El mismo concepto se aplica a

Delegate como un puntero al Method

La diferencia entre estas cosas es que los Delegate no poseen el concepto de OOP, por ejemplo, Inheritance . Para aclarar esto, hice lo experimental con

 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 comstacktion error!! // because fAssign is not the same KIND as customDlg //fAssign = customDlg; 

Muchos métodos integrados en el marco (por ejemplo, LINQ) reciben el parámetro de Func<> delegado. Lo que podemos hacer con este método es

Declare el delegado del tipo Func<> y páselo a la función en lugar de Define el delegado personalizado.

Por ejemplo, desde el código anterior agrego más código

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