web-dev-qa-db-fra.com

Quand ne pas utiliser d'expressions lambda

De nombreuses questions sont traitées dans Stack Overflow, les membres spécifiant comment résoudre ces problèmes réels/temporels en utilisant expressions lambda .

Sommes-nous en train d'en abuser et envisageons-nous l'impact sur les performances de l'utilisation des expressions lambda?

J'ai trouvé quelques articles qui explorent l'impact sur les performances des lambda vs délégués anonymes vs for/foreach boucles avec des résultats différents

  1. Délégués anonymes vs expressions Lambda vs performances des appels de fonction
  2. Performances de foreach vs. List.ForEach
  3. . Test de performance de boucle NET/C # (FOR, FOREACH, LINQ, & Lambda) .
  4. DataTable.Select est plus rapide que LINQ

Quels devraient être les critères d'évaluation lors du choix de la solution appropriée? Sauf pour la raison évidente qu'il s'agit d'un code plus concis et lisible lors de l'utilisation de lambda.

51
Binoj Antony

Même si je vais me concentrer sur le premier point, je commence par donner 2 cents sur toute la question de la performance. Sauf si les différences sont importantes ou si l'utilisation est intensive, je ne me soucie généralement pas des microsecondes qui, lorsqu'elles sont ajoutées, ne représentent aucune différence visible pour l'utilisateur. J'insiste sur le fait que je m'en fiche uniquement lorsque l'on considère des méthodes appelées non intensives. Là où j'ai des considérations de performances particulières, c'est sur la façon dont je conçois l'application elle-même. Je me soucie de la mise en cache, de l'utilisation des threads, des moyens intelligents d'appeler des méthodes (que ce soit pour faire plusieurs appels ou pour essayer de faire un seul appel), que ce soit pour regrouper les connexions ou non, etc., etc. pas se concentrer sur les performances brutes, mais sur l'évolutivité. Je me fiche qu'il fonctionne mieux d'une petite tranche de nanoseconde pour un seul utilisateur, mais je me soucie beaucoup d'avoir la possibilité de charger le système avec de grandes quantités d'utilisateurs simultanés sans remarquer l'impact.

Cela dit, voici mon opinion sur le point 1. J'adore les méthodes anonymes. Ils me donnent une grande flexibilité et une élégance de code. L'autre grande caractéristique des méthodes anonymes est qu'elles me permettent d'utiliser directement les variables locales à partir de la méthode conteneur (du point de vue C #, pas du point de vue IL, bien sûr). Ils m'épargnent souvent beaucoup de code. Quand dois-je utiliser des méthodes anonymes? Chaque fois, le morceau de code dont j'ai besoin n'est pas nécessaire ailleurs. S'il est utilisé à deux endroits différents, je n'aime pas le copier-coller comme technique de réutilisation, donc j'utiliserai un ancien délégué. Donc, tout comme Shoosh a répondu, il n'est pas bon d'avoir une duplication de code. En théorie, il n'y a pas de différences de performances car les anonymes sont des astuces C #, pas des trucs IL.

La plupart de ce que je pense des méthodes anonymes s'applique aux expressions lambda, car ces dernières peuvent être utilisées comme syntaxe compacte pour représenter des méthodes anonymes. Supposons la méthode suivante:

public static void DoSomethingMethod(string[] names, Func<string, bool> myExpression)
{
    Console.WriteLine("Lambda used to represent an anonymous method");
    foreach (var item in names)
    {
        if (myExpression(item))
            Console.WriteLine("Found {0}", item);
    }
}

Il reçoit un tableau de chaînes et pour chacune d'elles, il appellera la méthode qui lui est passée. Si cette méthode retourne vrai, elle dira "Trouvé ...". Vous pouvez appeler cette méthode de la manière suivante:

string[] names = {"Alice", "Bob", "Charles"};
DoSomethingMethod(names, delegate(string p) { return p == "Alice"; });

Mais, vous pouvez également l'appeler de la manière suivante:

DoSomethingMethod(names, p => p == "Alice");

Il n'y a pas de différence d'IL entre les deux, étant donné que celle qui utilise l'expression Lambda est beaucoup plus lisible. Encore une fois, il n'y a pas d'impact sur les performances car ce sont toutes des astuces de compilateur C # (pas des astuces de compilateur JIT). Tout comme je ne pensais pas que nous surutilisions des méthodes anonymes, je ne pense pas que nous surutilisions des expressions Lambda pour représenter des méthodes anonymes. Bien sûr, la même logique s'applique au code répété: ne faites pas de lambdas, utilisez des délégués réguliers. Il existe d'autres restrictions vous ramenant à des méthodes anonymes ou à des délégués simples, comme le passage d'arguments out ou ref.

Les autres choses intéressantes sur les expressions Lambda sont que la même syntaxe exacte n'a pas besoin de représenter une méthode anonyme. Les expressions lambda peuvent également représenter ... vous l'avez deviné, des expressions. Prenons l'exemple suivant:

public static void DoSomethingExpression(string[] names, System.Linq.Expressions.Expression<Func<string, bool>> myExpression)
{
    Console.WriteLine("Lambda used to represent an expression");
    BinaryExpression bExpr = myExpression.Body as BinaryExpression;
    if (bExpr == null)
        return;
    Console.WriteLine("It is a binary expression");
    Console.WriteLine("The node type is {0}", bExpr.NodeType.ToString());
    Console.WriteLine("The left side is {0}", bExpr.Left.NodeType.ToString());
    Console.WriteLine("The right side is {0}", bExpr.Right.NodeType.ToString());
    if (bExpr.Right.NodeType == ExpressionType.Constant)
    {
        ConstantExpression right = (ConstantExpression)bExpr.Right;
        Console.WriteLine("The value of the right side is {0}", right.Value.ToString());
    }
 }

Remarquez la signature légèrement différente. Le deuxième paramètre reçoit une expression et non un délégué. La façon d'appeler cette méthode serait:

DoSomethingExpression(names, p => p == "Alice");

C'est exactement la même chose que l'appel que nous avons fait lors de la création d'une méthode anonyme avec un lambda. La différence ici est que nous ne créons pas une méthode anonyme, mais créons un arbre d'expression. C'est grâce à ces arborescences d'expression que nous pouvons ensuite traduire des expressions lambda en SQL, ce que fait Linq 2 SQL, par exemple, au lieu d'exécuter des choses dans le moteur pour chaque clause comme Where, Select, etc. est que la syntaxe d'appel est la même que vous créiez une méthode anonyme ou que vous envoyiez une expression.

32
Rui Craveiro

Ma réponse ne sera pas populaire.

Je crois que les Lambda sont à 99% le meilleur choix pour trois raisons.

Tout d'abord, il n'y a absolument rien de mal à supposer que vos développeurs sont intelligents. D'autres réponses ont une prémisse sous-jacente que tout développeur, sauf vous, est stupide. Mais non.

Deuxièmement, les Lamdas (et al) sont une syntaxe moderne - et demain ils seront plus courants qu'ils ne le sont déjà aujourd'hui. Le code de votre projet doit découler des conventions actuelles et émergentes.

Troisièmement, écrire du code "à l'ancienne" peut vous sembler plus facile, mais ce n'est pas plus facile pour le compilateur. Ceci est important, les approches héritées ont peu de chances d'être améliorées lors de la révision du compilateur. Lambdas (et al) qui s'appuie sur le compilateur pour les développer peut en bénéficier car le compilateur les traite mieux au fil du temps.

Pour résumer:

  1. Les développeurs peuvent le gérer
  2. Tout le monde le fait
  3. Il y a un potentiel futur

Encore une fois, je sais que ce ne sera pas une réponse populaire. Et croyez-moi, "Simple is Best" est aussi mon mantra. La maintenance est un aspect important pour toute source. J'ai compris. Mais je pense que nous éclipsons la réalité avec quelques règles de base clichées.

// Jerry

25
Jerry Nixon - MSFT

Duplication de code.
Si vous vous retrouvez en train d'écrire plusieurs fois la même fonction anonyme, elle ne devrait pas en être une.

16
shoosh

Eh bien, lorsque nous parlons de l'utilisation des délégués, il ne devrait pas y avoir de différence entre lambda et les méthodes anonymes - elles sont les mêmes, juste avec une syntaxe différente. Et les méthodes nommées (utilisées comme délégués) sont également identiques du point de vue de l'exécution. La différence est donc entre l'utilisation de délégués et le code en ligne - c'est-à-dire.

list.ForEach(s=>s.Foo());
// vs.
foreach(var s in list) { s.Foo(); }

(où je m'attendrais à ce que ce dernier soit plus rapide)

Et également, si vous parlez de quelque chose autre que des objets en mémoire, les lambdas sont l'un de vos outils les plus puissants en termes de maintien de la vérification de type (plutôt que d'analyser les chaînes tout le temps).

Certes, il y a des cas où un simple foreach avec du code sera plus rapide que la version LINQ, car il y aura moins d'appels à faire et les appels coûtent un temps petit mais mesurable. Cependant, dans de nombreux cas, le code n'est tout simplement pas le goulot d'étranglement, et le code plus simple (en particulier pour le regroupement, etc.) vaut beaucoup plus que quelques nanosecondes.

Notez également que dans .NET 4.0, il y a nœuds Expression supplémentaires pour des choses comme les boucles, les virgules, etc. Le langage ne les prend pas en charge, mais le runtime le fait. Je ne mentionne cela que pour être complet: je ne dis certainement pas que vous devriez utiliser la construction manuelle de Expressionforeach ferait l'affaire!

14
Marc Gravell

Je dirais que les différences de performances sont généralement si faibles (et dans le cas des boucles, évidemment, si vous regardez les résultats du 2ème article (btw, Jon Skeet a un article similaire ici ) ) que vous ne devriez presque jamais choisir une solution pour des raisons de performances uniquement, sauf si vous écrivez un logiciel où les performances sont absolument le numéro un non fonctionnel et vous devez vraiment faire des micro-optimisations.

Quand choisir quoi? Je suppose que cela dépend de la situation mais aussi de la personne. À titre d'exemple, certaines personnes préfèrent List.Foreach sur une boucle foreach normale. Je personnellement préfère ce dernier, car il est généralement plus lisible, mais qui suis-je pour contester cela?

6
Razzie

Règles de base:

  1. Écrivez votre code pour qu'il soit naturel et lisible.
  2. Évitez les duplications de code (les expressions lambda peuvent nécessiter un peu de diligence supplémentaire).
  3. Optimisez uniquement en cas de problème, et uniquement avec des données pour sauvegarder ce qu'est réellement ce problème.
4
Dustin Campbell

Chaque fois que le lambda passe simplement ses arguments directement à une autre fonction. Ne créez pas de lambda pour l'application de fonction.

Exemple:

var coll = new ObservableCollection<int>();
myInts.ForEach(x => coll.Add(x))

Est plus agréable que:

var coll = new ObservableCollection<int>();
myInts.ForEach(coll.Add)

La principale exception est lorsque l'inférence de type C # échoue pour une raison quelconque (et il y a de nombreuses fois c'est vrai).

4
MichaelGG

Les expressions lambda sont cool. Par rapport à l'ancienne syntaxe delegate, ils ont quelques avantages comme, ils peuvent être convertis en arborescence de fonctions ou d'expressions anonymes, les types de paramètres sont déduits d'après la déclaration, ils sont plus propres et plus concis, etc. Je ne vois aucune valeur réelle à ne pas utiliser l'expression lambda lorsque vous avez besoin d'une fonction anonyme. Un avantage moins important du style précédent est que vous pouvez omettre totalement la déclaration de paramètre si elles ne sont pas utilisées. Comme

Action<int> a = delegate { }; //takes one argument, but no argument specified

C'est utile lorsque vous devez déclarer un délégué vide qui ne fait rien, mais ce n'est pas une raison forte suffisante pour ne pas utiliser lambdas.

Lambdas vous permet d'écrire des méthodes anonymes rapides. Cela rend les lambdas dénués de sens partout où les méthodes anonymes sont dénuées de sens, c'est-à-dire là où les méthodes nommées ont plus de sens. Au-dessus des méthodes nommées , les méthodes anonymes peuvent être désavantageuses (pas une expression lambda en soi, mais puisque ces jours-ci les lambdas représentent largement les méthodes anonymes, elles sont pertinentes):

  1. car il a tendance à conduire à une duplication logique (souvent, la réutilisation est difficile)

  2. quand il n'est pas nécessaire d'en écrire un, comme:

    //this is unnecessary 
    Func<string, int> f = x => int.Parse(x);
    
    //this is enough
    Func<string, int> f = int.Parse;
    
  3. car écrire un bloc itérateur anonyme est impossible.

    Func<IEnumerable<int>> f = () => { yield return 0; }; //impossible
    
  4. puisque les lambdas récursifs nécessitent une autre ligne de bizarrerie, comme

    Func<int, int> f = null;
    f = x => (x <= 1) ? 1 : x * f(x - 1);
    
  5. bien, puisque la réflexion est un peu plus désordonnée, mais c'est théorique non?

Mis à part le point 3, les autres ne sont pas forts raisons ne pas utiliser lambdas.

Voir aussi ce thread sur ce qui est désavantageux sur Func/Action délégués, car ils sont souvent utilisés avec des expressions lambda.

2
nawfal

Si vous avez besoin de récursivité, n'utilisez pas de lambdas, ou vous finirez par être très distrait !

2
Daniel Earwicker