web-dev-qa-db-fra.com

Création de délégués manuellement vs utilisation de délégués Action / Func

Aujourd'hui, je pensais à déclarer ceci:

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

mais pourquoi ne pas utiliser ceci:

private Func<string, int, double> ChangeListAction;

ou si ChangeListAction n'aurait aucune valeur de retour que je pourrais utiliser:

private Action<string,int> ChangeListAction;

alors où est l'avantage de déclarer un délégué avec le mot clé delegate?

Est-ce à cause de .NET 1.1, et avec .NET 2.0 est venu Action<T> et avec .NET 3.5 est venu Func<T>?

69
Elisabeth

L'avantage est la clarté. En donnant au type un nom explicite, il est plus clair pour le lecteur ce qu'il fait.

Cela vous aidera également lors de l'écriture du code. Une erreur comme celle-ci:

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

est moins utile que celui qui dit:

cannot convert from CreateListAction to UpdateListAction

Cela signifie également que si vous avez deux délégués différents, qui prennent tous les deux les mêmes types de paramètres mais font conceptuellement deux choses complètement différentes, le compilateur peut garantir que vous ne pouvez pas utiliser accidentellement l'un là où vous vouliez dire l'autre.

52
Mark Byers

L'avènement des familles de délégués Action et Func a rendu les délégués personnalisés moins utilisés, mais ce dernier trouve toujours des utilisations. Les avantages des délégués personnalisés incluent:

  1. Comme d'autres l'ont souligné, exprime clairement l'intention contrairement aux génériques Action et Func ( Patrik a un très bon point sur les noms de paramètres significatifs).

  2. Vous pouvez spécifier les paramètres ref/out contrairement aux deux autres délégués génériques. Par exemple, vous pouvez avoir

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

    mais non

    Func<out string, ref int, double> ChangeListAction;
    
  3. De plus, avec les délégués personnalisés, vous devez écrire ChangeListAction (je veux dire la définition) une seule fois quelque part dans votre base de code, alors que si vous n'en définissez pas un, vous devrez le jeter partout Func<string, int, double> partout. Changer la signature sera un problème dans ce dernier cas - un mauvais cas de ne pas être sec.

  4. Peut avoir des paramètres facultatifs.

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

    mais non

    Func<string, int, double> ChangeListAction = (p1 = "haha", p2) => (double)p2; 
    
  5. Vous pouvez avoir un mot clé params pour les paramètres d'une méthode, pas avec Action/Func.

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

    mais non

    Func<int, params string[], double> ChangeListAction;
    
  6. Eh bien, si vous n'avez vraiment pas de chance et avez besoin de plus de 16 paramètres (pour le moment) :)


Quant aux mérites de Action et Func:

  1. C'est rapide et sale, et je l'utilise partout. Cela rend le code court si le cas d'utilisation est trivial (les délégués personnalisés sont passés de mode avec moi).

  2. Plus important encore, son type est compatible entre les domaines. Action et Func sont définis par le framework et fonctionnent de manière transparente tant que les types de paramètres correspondent. Vous ne pouvez pas avoir ChangeSomeAction pour ChangeListAction. Linq trouve très utile cet aspect.

69
nawfal

La déclaration explicite d'un délégué peut aider à certaines vérifications de type. Le compilateur peut s'assurer que le délégué affecté à la variable est destiné à être utilisé comme ChangeListAction et non à une action aléatoire qui se trouve être compatible avec la signature.

Cependant, la vraie valeur de déclarer votre propre délégué est qu'il lui donne un sens sémantique. Une personne lisant le code saura ce que le délégué fait par son nom. Imaginez si vous aviez une classe avec trois champs int mais à la place vous avez déclaré un tableau de trois éléments int. Le tableau peut faire la même chose mais les noms des champs apportent des informations sémantiques utiles aux développeurs.

Vous devez utiliser les délégués Func, Predicate et Action lorsque vous concevez une bibliothèque à usage général comme LINQ. Dans ce cas, les délégués n'ont pas de sémantique prédéfinie autre que le fait qu'ils s'exécuteront et agiront ou seront utilisés comme prédicat.

Sur une note secondaire, il existe un problème de compromis similaire avec Tuple vs type anonyme vs déclarer votre propre classe. Vous pouvez simplement tout coller dans un Tuple mais les propriétés ne sont que Item1, Item2 qui ne dit rien sur l'utilisation du type.

11
Stilgar

Comme certaines réponses mentionnent que la victoire est la clarté, vous nommez le type et il sera donc plus facile à comprendre pour un utilisateur de votre API. Je dirais - dans la plupart des cas - déclarer des types de délégué pour vos API publiques, mais il est tout à fait correct d'utiliser Func<?,?> en interne.

Un énorme avantage de déclarer le type délégué qui n'est pas mentionné dans les autres réponses est qu'en plus de donner un nom au type, vous pouvez également nommer les paramètres, cela augmentera considérablement l'utilisabilité.

7
Patrik Hägne

J'ai trouvé un cas d'utilisation spécial où vous ne pouvez utiliser que délégué:

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

L'utilisation de Func/Action ne fonctionne tout simplement pas: '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);

Le code suivant compile, mais lève une exception lors de l'exécution car System.Runtime.InteropServices.DllImportAttribute ne prend pas en charge le marshaling des types génériques:

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

Je présente cet exemple pour montrer à tout le monde que: parfois, déléguer est votre seul choix. Et ceci est une réponse raisonnable à votre question why not use Action<T>/Func<T> ?

6
Tyler Long

Déclarez le délégué explicitement lorsque vous commencez à obtenir trop de paramètres dans le Func/Action, sinon vous devez continuer à regarder en arrière, "Quelle est la deuxième signification int?"

5
Ana Betts

Pour une réponse meilleure et plus élaborée, consultez @nawfal. J'essaierai d'être plus simpliste.

Vous déclarez un membre d'une classe, vous devez donc vous en tenir à délégué. L'utilisation de delegate est plus descriptive et structurelle.

Les types Action/Func Sont faits pour être distribués, vous devez donc les utiliser davantage comme paramètres et variables locales.

Et en fait, ces deux héritent de la classe Delegate. Action et Func sont des types génériques et simplifient la création de délégués avec différents types de paramètres. Et le mot clé delegate crée en fait une toute nouvelle classe héritant de Delegate dans une seule déclaration.

2
Bizniztime

Comme MSDN dit, Func<> est lui-même prédéfini Delegate. Pour la première fois, j'ai confondu ce genre de choses. Après l'expérimentation, ma compréhension était plus claire. Normalement, en C #, on peut voir

Type comme pointeur vers Instance.

Le même concept est appliqué à

Delegate comme pointeur vers Method

La différence entre ces éléments et les choses est que Delegate ne possède pas le concept de POO, par exemple, Inheritance. Pour rendre les choses plus claires, j'ai fait l'expérimentation avec

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;

De nombreuses méthodes intégrées dans le cadre (par exemple, LINQ), reçoivent le paramètre de Func<> délégué. La chose que nous pouvons faire avec cette méthode est

Declare le délégué de Func<> tapez et transmettez-le à la fonction plutôt que Define au délégué personnalisé.

Par exemple, à partir du code ci-dessus, j'ajoute plus de code

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