Funzione locale vs Lambda C # 7.0


178

Sto esaminando le nuove implementazioni in C # 7.0 e trovo interessante che abbiano implementato funzioni locali ma non riesco a immaginare uno scenario in cui una funzione locale sarebbe preferita su un'espressione lambda e qual è la differenza tra i due.

Capisco che le lambda sono anonymousfunzioni mentre le funzioni locali non lo sono, ma non riesco a capire uno scenario del mondo reale, in cui la funzione locale presenta vantaggi rispetto alle espressioni lambda

Qualsiasi esempio sarebbe molto apprezzato. Grazie.


9
Generici, parametri di uscita, funzioni ricorsive senza dover inizializzare la lambda su null, ecc.
Kirk Woll,

5
@KirkWoll - Dovresti pubblicare questo come risposta.
Enigmatività il

Risposte:


276

Ciò è stato spiegato da Mads Torgersen in C # Design Meeting Notes dove sono state discusse le funzioni locali :

Vuoi una funzione di aiuto. Lo stai usando solo all'interno di una singola funzione e probabilmente utilizza variabili e parametri di tipo che rientrano nell'ambito di quella funzione contenente. D'altra parte, a differenza di una lambda, non è necessario come oggetto di prima classe, quindi non ti interessa dargli un tipo delegato e allocare un oggetto delegato effettivo. Inoltre, potresti voler che sia ricorsivo o generico o implementarlo come iteratore.

Per espanderlo ancora, i vantaggi sono:

  1. Prestazione.

    Quando si crea un lambda, è necessario creare un delegato, che in questo caso è un'assegnazione non necessaria. Le funzioni locali sono in realtà solo funzioni, non sono necessari delegati.

    Inoltre, le funzioni locali sono più efficienti nel catturare variabili locali: le lambda di solito catturano variabili in una classe, mentre le funzioni locali possono usare una struttura (passata usando ref), che evita nuovamente un'allocazione.

    Ciò significa anche che chiamare le funzioni locali è più economico e possono essere integrate, aumentando eventualmente le prestazioni ulteriormente.

  2. Le funzioni locali possono essere ricorsive.

    Anche Lambdas può essere ricorsivo, ma richiede un codice scomodo, in cui si assegna prima nulluna variabile delegata e poi lambda. Le funzioni locali possono naturalmente essere ricorsive (comprese quelle reciprocamente ricorsive).

  3. Le funzioni locali possono essere generiche.

    Le lambda non possono essere generiche, poiché devono essere assegnate a una variabile con un tipo concreto (quel tipo può usare variabili generiche dall'ambito esterno, ma non è la stessa cosa).

  4. Le funzioni locali possono essere implementate come iteratore.

    Lambdas non può usare la parola chiave yield return(e yield break) per implementare la IEnumerable<T>funzione di ritorno. Le funzioni locali possono.

  5. Le funzioni locali sembrano migliori.

    Questo non è menzionato nella citazione sopra e potrebbe essere solo un mio pregiudizio personale, ma penso che la normale sintassi delle funzioni appaia migliore rispetto all'assegnazione di una lambda a una variabile delegata. Le funzioni locali sono anche più succinte.

    Confrontare:

    int add(int x, int y) => x + y;
    Func<int, int, int> add = (x, y) => x + y;

22
Vorrei aggiungere che le funzioni locali hanno nomi di parametri sul lato chiamante. Lambdas no.
Lensflare,

3
@Lensflare È vero che i nomi dei parametri di lambdas non vengono conservati, ma è perché devono essere convertiti in delegati, che hanno i loro nomi. Ad esempio: Func<int, int, int> f = (x, y) => x + y; f(arg1:1, arg2:1);.
svick

1
Ottima lista! Tuttavia, posso immaginare come il compilatore IL / JIT potrebbe eseguire tutte le ottimizzazioni menzionate in 1. anche per i delegati se il loro utilizzo aderisce a determinate regole.
Marcin Kaczmarek,

1
@Casebash Perché lambdas usa sempre un delegato e quel delegato detiene la chiusura come object. Quindi, lambda potrebbe usare una struttura, ma dovrebbe essere inscatolata, quindi avresti ancora quella allocazione aggiuntiva.
svick

1
@happybits Principalmente quando non è necessario assegnargli un nome, come quando lo si passa al metodo.
svick

83

Oltre alla grande risposta di svick, c'è un ulteriore vantaggio nelle funzioni locali:
possono essere definite in qualsiasi punto della funzione, anche dopo l' returnistruzione.

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;
}

5
Questo è davvero utile, poiché posso abituarmi a mettere tutte le funzioni di aiuto in a #region Helpersnella parte inferiore della funzione, in modo da evitare disordine all'interno di quella funzione ed in particolare evitare disordine nella classe principale.
AustinWBryan,

Lo apprezzo anche io. Rende la funzione principale che stai guardando più facile da leggere, in quanto non è necessario guardarsi intorno per trovare da dove inizia. Se vuoi vedere i dettagli dell'implementazione, continua a guardare oltre la fine.
Remi Despres-Smyth,

3
se le tue funzioni sono così grandi che hanno bisogno di regioni, sono troppo grandi.
fabbro

9

Se ti chiedi anche come testare la funzione locale, dovresti controllare JustMock in quanto ha la funzionalità per farlo. Ecco un semplice esempio di classe che verrà testato:

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

Ed ecco come appare il 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); 
    } 
} 

Ecco un link alla documentazione di JustMock .

Esclusione di responsabilità. Sono uno degli sviluppatori responsabili di JustMock .


è bello vedere sviluppatori così appassionati che sostengono che le persone utilizzino il loro strumento. Come ti sei interessato a scrivere strumenti per sviluppatori come lavoro a tempo pieno? Come americano, la mia impressione è che può essere difficile trovare tali carriere a meno che tu non abbia un master o un dottorato di ricerca. in comp sci.
John Zabroski,

Ciao John e grazie per le belle parole. Come sviluppatore di software, non vedo niente di meglio che essere apprezzato dai miei clienti per il valore che offro loro. Combina questo con il desiderio di un lavoro stimolante e competitivo e riceverai un elenco abbastanza limitato di cose di cui sarei appassionato. La scrittura degli strumenti per gli sviluppatori di produttività è in quell'elenco. Almeno nella mia mente :) Per quanto riguarda la carriera, penso che le aziende che forniscono strumenti per sviluppatori siano una percentuale piuttosto piccola di tutte le società di software ed è per questo che è più difficile trovare una simile opportunità.
Mihail Vladov,

Una domanda separata. Perché non chiami VerifyAll qui? C'è un modo per dire a JustMock di verificare che anche la funzione locale sia stata chiamata?
John Zabroski l'

2
Ciao @JohnZabroski, lo scenario testato non ha richiesto affermazioni. Naturalmente, è possibile verificare che sia stata effettuata una chiamata. Innanzitutto, devi specificare quante volte ti aspetti che venga chiamato il metodo. In questo modo: .DoNothing().OccursOnce();E in seguito affermare che la chiamata è stata effettuata chiamando il Mock.Assert(foo);metodo. Se sei interessato a come sono supportati altri scenari, puoi leggere l' articolo della guida Asserting Occurrence .
Mihail Vladov,

0

Uso le funzioni incorporate per evitare la pressione della raccolta dei rifiuti specialmente quando ho a che fare con metodi più lunghi. Supponiamo che si desideri ottenere 2 anni o dati di mercato per un determinato simbolo di ticker. Inoltre, se necessario, è possibile impacchettare molte funzionalità e logica aziendale.

ciò che si fa è aprire una connessione socket al server e passare in rassegna i dati collegando un evento a un evento. Si può pensare allo stesso modo in cui una classe è progettata, solo uno non sta scrivendo metodi di supporto in tutto il luogo che funzionano davvero solo per un pezzo di funzionalità. di seguito è riportato un esempio di come potrebbe apparire, si prega di notare che sto usando le variabili e i metodi "helper" sono al di sotto di quelli finalmente disponibili. In Infine rimuovo bene i gestori di eventi, se la mia classe di Exchange fosse esterna / iniettata non avrei registrato alcun gestore di eventi in sospeso

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;
   } 
}

Puoi vedere i vantaggi come indicato di seguito, qui puoi vedere un'implementazione di esempio. Spero che questo aiuti a spiegare i benefici.


2
1. Questo è un esempio e una spiegazione davvero complessi solo per dimostrare le funzioni locali. 2. Le funzioni locali non evitano alcuna allocazione rispetto a lambda in questo esempio, poiché devono ancora essere convertite in delegati. Quindi non vedo come eviterebbero GC.
svick,

1
non passando / copiando le variabili, la risposta di svick copre il resto molto bene. Non c'è bisogno di duplicare la sua risposta
Walter Vehoeven,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.