Fonction locale vs Lambda C # 7.0

178

Je regarde les nouvelles implémentations en C # 7.0 et je trouve intéressant qu'elles aient implémenté des fonctions locales mais je ne peux pas imaginer un scénario où une fonction locale serait préférée à une expression lambda, et quelle est la différence entre les deux.

Je comprends que les lambdas sont des anonymousfonctions alors que les fonctions locales ne le sont pas, mais je ne peux pas comprendre un scénario du monde réel, où la fonction locale a des avantages par rapport aux expressions lambda

Tout exemple serait très apprécié. Merci.

Sid
la source
9
Génériques, paramètres de sortie, fonctions récursives sans avoir à initialiser le lambda à null, etc.
Kirk Woll
5
@KirkWoll - Vous devriez poster ceci comme réponse.
Enigmativity

Réponses:

276

Cela a été expliqué par Mads Torgersen dans C # Design Meeting Notes où les fonctions locales ont été abordées pour la première fois :

Vous voulez une fonction d'assistance. Vous ne l'utilisez qu'à partir d'une seule fonction, et il utilise probablement des variables et des paramètres de type qui sont dans la portée de cette fonction contenant. D'un autre côté, contrairement à un lambda, vous n'en avez pas besoin en tant qu'objet de première classe, vous ne vous souciez donc pas de lui donner un type de délégué et d'allouer un objet délégué réel. Vous pouvez également souhaiter qu'il soit récursif ou générique, ou qu'il soit implémenté en tant qu'itérateur.

Pour approfondir encore, les avantages sont:

  1. Performance.

    Lors de la création d'un lambda, un délégué doit être créé, ce qui est une allocation inutile dans ce cas. Les fonctions locales ne sont en réalité que des fonctions, aucun délégué n'est nécessaire.

    En outre, les fonctions locales sont plus efficaces pour capturer les variables locales: les lambdas capturent généralement des variables dans une classe, tandis que les fonctions locales peuvent utiliser une structure (passée à l'aide de ref), ce qui évite à nouveau une allocation.

    Cela signifie également que l'appel des fonctions locales est moins cher et qu'elles peuvent être intégrées, augmentant éventuellement encore davantage les performances.

  2. Les fonctions locales peuvent être récursives.

    Les lambdas peuvent également être récursifs, mais cela nécessite un code maladroit, où vous affectez d'abord nullà une variable de délégué, puis au lambda. Les fonctions locales peuvent naturellement être récursives (y compris mutuellement récursives).

  3. Les fonctions locales peuvent être génériques.

    Les lambdas ne peuvent pas être génériques, car ils doivent être affectés à une variable avec un type concret (ce type peut utiliser des variables génériques de la portée externe, mais ce n'est pas la même chose).

  4. Les fonctions locales peuvent être implémentées sous forme d'itérateur.

    Lambdas ne peut pas utiliser le mot-clé yield return(et yield break) pour implémenter la IEnumerable<T>fonction -returning. Les fonctions locales peuvent.

  5. Les fonctions locales sont plus belles.

    Cela n'est pas mentionné dans la citation ci-dessus et pourrait être juste mon parti pris personnel, mais je pense que la syntaxe de fonction normale semble meilleure que d'assigner un lambda à une variable de délégué. Les fonctions locales sont également plus succinctes.

    Comparer:

    int add(int x, int y) => x + y;
    Func<int, int, int> add = (x, y) => x + y;
svick
la source
22
Je voudrais ajouter que les fonctions locales ont des noms de paramètres du côté de l'appelant. Les lambdas ne le font pas.
Lensflare le
3
@Lensflare Il est vrai que les noms de paramètres des lambdas ne sont pas conservés, mais c'est parce qu'ils doivent être convertis en délégués, qui ont leurs propres noms. Par exemple: Func<int, int, int> f = (x, y) => x + y; f(arg1:1, arg2:1);.
svick
1
Super liste! Cependant, je peux imaginer comment le compilateur IL / JIT pourrait effectuer toutes les optimisations mentionnées en 1. également pour les délégués si leur utilisation respecte certaines règles.
Marcin Kaczmarek
1
@Casebash Parce que les lambdas utilisent toujours un délégué et que ce délégué détient la fermeture en tant que object. Ainsi, lambdas pourrait utiliser une structure, mais elle devrait être encadrée, donc vous auriez toujours cette allocation supplémentaire.
svick
1
@happybits Surtout lorsque vous n'avez pas besoin de lui donner un nom, comme lorsque vous le passez à method.
svick
83

En plus de la bonne réponse de svick, il y a un autre avantage aux fonctions locales:
elles peuvent être définies n'importe où dans la fonction, même après l' returninstruction.

public double DoMath(double a, double b)
{
    var resultA = f(a);
    var resultB = f(b);
    return resultA + resultB;

    double f(double x) => 5 * x + 3;
}
Tim Pohlmann
la source
5
C'est vraiment utile, car je peux m'habituer à mettre toutes les fonctions d'assistance dans un #region Helpersau bas de la fonction, afin d'éviter l'encombrement dans cette fonction et surtout éviter l'encombrement dans la classe principale.
AustinWBryan
J'apprécie également cela. Cela facilite la lecture de la fonction principale que vous recherchez, car vous n'avez pas besoin de regarder autour de vous pour trouver où elle commence. Si vous voulez voir les détails de l'implémentation, continuez à regarder au-delà de la fin.
Remi Despres-Smyth
3
si vos fonctions sont si grandes qu'elles ont besoin de régions, elles sont trop grandes.
ssmith
9

Si vous vous demandez également comment tester la fonction locale, vous devriez vérifier JustMock car il a la fonctionnalité pour le faire. Voici un exemple de classe simple qui sera testé:

public class Foo // the class under test
{ 
    public int GetResult() 
    { 
        return 100 + GetLocal(); 
        int GetLocal () 
        { 
            return 42; 
        } 
    } 
}

Et voici à quoi ressemble le test:

[TestClass] 
public class MockLocalFunctions 
{ 
    [TestMethod] 
    public void BasicUsage() 
    { 
        //Arrange 
        var foo = Mock.Create<Foo>(Behavior.CallOriginal); 
        Mock.Local.Function.Arrange<int>(foo, "GetResult", "GetLocal").DoNothing(); 

        //Act 
        var result = foo. GetResult(); 

        //Assert 
        Assert.AreEqual(100, result); 
    } 
} 

Voici un lien vers la documentation JustMock .

Avertissement. Je suis l'un des développeurs responsables de JustMock .

Mihail Vladov
la source
c'est formidable de voir des développeurs aussi passionnés plaider pour que les gens utilisent leur outil. Comment vous êtes-vous intéressé à la rédaction d'outils de développement en tant que travail à temps plein? En tant qu'Américain, j'ai l'impression qu'il peut être difficile de trouver de telles carrières à moins que vous n'ayez une maîtrise ou un doctorat. dans comp sci.
John Zabroski
Salut John et merci pour les aimables paroles. En tant que développeur de logiciels, je ne vois rien de mieux que d'être apprécié par mes clients pour la valeur que je leur apporte. Combinez cela avec le désir d'un travail stimulant et compétitif et vous recevrez une liste assez limitée de choses qui me passionnent. L'écriture d'outils de développement de productivité fait partie de cette liste. Au moins dans mon esprit :) En ce qui concerne la carrière, je pense que les entreprises fournissant des outils de développement ne représentent qu'un petit pourcentage de toutes les éditeurs de logiciels et c'est pourquoi il est plus difficile de trouver une telle opportunité.
Mihail Vladov
Une question distincte. Pourquoi n'appelez-vous pas VerifyAll ici? Existe-t-il un moyen de dire à JustMock de vérifier que la fonction locale a également été appelée?
John Zabroski
2
Salut @JohnZabroski, le scénario testé ne nécessitait pas d'affirmer les occurrences. Bien sûr, vous pouvez vérifier qu'un appel a été effectué. Tout d'abord, vous devez spécifier combien de fois vous attendez que la méthode soit appelée. Comme ceci: .DoNothing().OccursOnce();Et affirmer plus tard que l'appel a été effectué en appelant la Mock.Assert(foo);méthode. Si vous souhaitez savoir comment d'autres scénarios sont pris en charge, vous pouvez lire notre article d'aide Asserting Occurrence .
Mihail Vladov
0

J'utilise des fonctions en ligne pour éviter la pression du ramasse-miettes en particulier lorsque je traite des méthodes plus longues. Supposons que l'on souhaite obtenir 2 ans ou des données de marché pour un symbole boursier donné. En outre, on peut emballer beaucoup de fonctionnalités et de logique métier si nécessaire.

ce que l'on fait est d'ouvrir une connexion socket au serveur et de boucler sur les données liant un événement à un événement. On peut y penser de la même manière qu'une classe est conçue, une seule n'écrit pas de méthodes d'assistance partout qui ne fonctionnent vraiment que pour une seule fonctionnalité. ci-dessous est un exemple de ce à quoi cela pourrait ressembler, veuillez noter que j'utilise des variables et que les méthodes "d'assistance" sont en dessous de la méthode. Dans le Enfin, je supprime joliment les gestionnaires d'événements, si ma classe Exchange était externe / injectée, je n'aurais aucun gestionnaire d'événements en attente enregistré

void List<HistoricalData> RequestData(Ticker ticker, TimeSpan timeout)
{
    var socket= new Exchange(ticker);
    bool done=false;
    socket.OnData += _onData;
    socket.OnDone += _onDone;
    var request= NextRequestNr();
    var result = new List<HistoricalData>();
    var start= DateTime.Now;
    socket.RequestHistoricalData(requestId:request:days:1);
    try
    {
      while(!done)
      {   //stop when take to long….
        if((DateTime.Now-start)>timeout)
           break;
      }
      return result;

    }finally
    {
        socket.OnData-=_onData;
        socket.OnDone-= _onDone;
    }


   void _OnData(object sender, HistoricalData data)
   {
       _result.Add(data);
   }
   void _onDone(object sender, EndEventArgs args)
   {
      if(args.ReqId==request )
         done=true;
   } 
}

Vous pouvez voir les avantages mentionnés ci-dessous, ici vous pouvez voir un exemple d'implémentation. J'espère que cela aide à expliquer les avantages.

Walter Vehoeven
la source
2
1. C'est un exemple et une explication très complexes juste pour démontrer les fonctions locales. 2. Les fonctions locales n'évitent aucune allocation par rapport aux lambdas dans cet exemple, car elles doivent encore être converties en délégués. Donc je ne vois pas comment ils éviteraient GC.
svick
1
ne pas passer / copier des variables, la réponse de svick couvre très bien le reste. Pas besoin de dupliquer sa réponse
Walter Vehoeven