Lokale Funktion gegen Lambda C # 7.0

178

Ich schaue mir die neuen Implementierungen in C # 7.0 an und finde es interessant, dass sie lokale Funktionen implementiert haben, aber ich kann mir kein Szenario vorstellen, in dem eine lokale Funktion einem Lambda-Ausdruck vorgezogen würde und was der Unterschied zwischen beiden ist.

Ich verstehe, dass Lambdas anonymousFunktionen sind, während lokale Funktionen dies nicht sind, aber ich kann mir kein reales Szenario vorstellen, in dem lokale Funktionen Vorteile gegenüber Lambda-Ausdrücken haben

Jedes Beispiel wäre sehr dankbar. Vielen Dank.

Sid
quelle
9
Generika, Out-Parameter, rekursive Funktionen, ohne das Lambda auf Null initialisieren zu müssen usw.
Kirk Woll
5
@KirkWoll - Du solltest dies als Antwort posten.
Rätselhaftigkeit

Antworten:

276

Dies wurde von Mads Torgersen in C # Design Meeting Notes erklärt, in denen lokale Funktionen zuerst besprochen wurden :

Sie möchten eine Hilfsfunktion. Sie verwenden es nur innerhalb einer einzelnen Funktion, und es werden wahrscheinlich Variablen und Typparameter verwendet, die in der enthaltenen Funktion enthalten sind. Im Gegensatz zu einem Lambda benötigen Sie es jedoch nicht als erstklassiges Objekt, sodass Sie ihm keinen Delegatentyp zuweisen und kein tatsächliches Delegatenobjekt zuweisen möchten. Möglicherweise möchten Sie auch, dass es rekursiv oder generisch ist oder als Iterator implementiert wird.

Um es noch weiter zu erweitern, sind die Vorteile:

  1. Performance.

    Beim Erstellen eines Lambda muss ein Delegat erstellt werden, was in diesem Fall eine unnötige Zuordnung darstellt. Lokale Funktionen sind eigentlich nur Funktionen, es sind keine Delegierten erforderlich.

    Außerdem sind lokale Funktionen beim Erfassen lokaler Variablen effizienter: Lambdas erfassen normalerweise Variablen in einer Klasse, während lokale Funktionen eine Struktur (übergeben mit ref) verwenden können, wodurch wiederum eine Zuordnung vermieden wird.

    Dies bedeutet auch, dass das Aufrufen lokaler Funktionen billiger ist und eingebunden werden kann, wodurch die Leistung möglicherweise noch weiter gesteigert wird.

  2. Lokale Funktionen können rekursiv sein.

    Lambdas können auch rekursiv sein, erfordern jedoch umständlichen Code, bei dem Sie zuerst nulleiner Delegatenvariablen und dann dem Lambda zuweisen . Lokale Funktionen können natürlich rekursiv sein (einschließlich gegenseitig rekursiv).

  3. Lokale Funktionen können generisch sein.

    Lambdas können nicht generisch sein, da sie einer Variablen mit einem konkreten Typ zugewiesen werden müssen (dieser Typ kann generische Variablen aus dem äußeren Bereich verwenden, aber das ist nicht dasselbe).

  4. Lokale Funktionen können als Iterator implementiert werden.

    Lambdas kann das Schlüsselwort yield return(und yield break) nicht verwenden , um die IEnumerable<T>Rückgabefunktion zu implementieren . Lokale Funktionen können.

  5. Lokale Funktionen sehen besser aus.

    Dies wird im obigen Zitat nicht erwähnt und ist möglicherweise nur meine persönliche Neigung, aber ich denke, dass die normale Funktionssyntax besser aussieht, als einer Delegatenvariablen ein Lambda zuzuweisen. Lokale Funktionen sind auch prägnanter.

    Vergleichen Sie:

    int add(int x, int y) => x + y;
    Func<int, int, int> add = (x, y) => x + y;
svick
quelle
22
Ich möchte hinzufügen, dass lokale Funktionen auf der Aufruferseite Parameternamen haben. Lambdas nicht.
Lensflare
3
@Lensflare Es stimmt, dass Parameternamen von Lambdas nicht beibehalten werden, aber das liegt daran, dass sie in Delegaten konvertiert werden müssen, die ihre eigenen Namen haben. Zum Beispiel : Func<int, int, int> f = (x, y) => x + y; f(arg1:1, arg2:1);.
Svick
1
Tolle Liste! Ich kann mir jedoch vorstellen, wie der IL / JIT-Compiler alle unter 1. genannten Optimierungen auch für Delegierte durchführen könnte, wenn deren Verwendung bestimmten Regeln entspricht.
Marcin Kaczmarek
1
@Casebash Da Lambdas immer einen Delegaten verwenden und dieser Delegat den Abschluss als object. Lambdas könnten also eine Struktur verwenden, aber sie müsste eingerahmt werden, damit Sie immer noch diese zusätzliche Zuordnung haben.
Svick
1
@happybits Meistens, wenn Sie ihm keinen Namen geben müssen, z. B. wenn Sie ihn an method übergeben.
Svick
83

Neben der großartigen Antwort von svick bieten lokale Funktionen noch einen weiteren Vorteil:
Sie können überall in der Funktion definiert werden, auch nach der returnAnweisung.

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
quelle
5
Dies ist sehr nützlich, da ich mich daran #region Helpersgewöhnen kann, alle Hilfsfunktionen am unteren Rand der Funktion zu platzieren, um Unordnung innerhalb dieser Funktion und insbesondere Unordnung in der Hauptklasse zu vermeiden.
AustinWBryan
Ich weiß das auch zu schätzen. Dadurch wird die Hauptfunktion, die Sie betrachten, leichter lesbar, da Sie sich nicht umschauen müssen, um herauszufinden, wo sie beginnt. Wenn Sie die Implementierungsdetails anzeigen möchten, schauen Sie über das Ende hinaus.
Remi Despres-Smyth
3
Wenn Ihre Funktionen so groß sind, dass sie Regionen benötigen, sind sie zu groß.
Schmied
9

Wenn Sie sich auch fragen, wie Sie die lokale Funktion testen können, sollten Sie JustMock überprüfen, da es über die entsprechenden Funktionen verfügt. Hier ist ein einfaches Klassenbeispiel, das getestet wird:

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

Und so sieht der Test aus:

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

Hier ist ein Link zur JustMock- Dokumentation .

Haftungsausschluss. Ich bin einer der Entwickler, die für JustMock verantwortlich sind .

Mihail Vladov
quelle
Es ist großartig zu sehen, wie leidenschaftliche Entwickler sich dafür einsetzen, dass Menschen ihr Tool verwenden. Wie sind Sie dazu gekommen, Entwicklertools als Vollzeitjob zu schreiben? Als Amerikaner habe ich den Eindruck, dass es schwierig sein kann, solche Karrieren zu finden, wenn Sie keinen Master oder Ph.D. haben. in comp sci.
John Zabroski
Hallo John und danke für die freundlichen Worte. Als Softwareentwickler sehe ich nichts Besseres, als von meinen Kunden für den Wert, den ich ihnen biete, geschätzt zu werden. Wenn Sie dies mit dem Wunsch nach herausfordernder und wettbewerbsfähiger Arbeit kombinieren, erhalten Sie eine ziemlich begrenzte Liste von Dingen, für die ich eine Leidenschaft hätte. Das Schreiben von Produktivitätsentwickler-Tools steht auf dieser Liste. Zumindest in meinen Gedanken :) In Bezug auf die Karriere denke ich, dass die Unternehmen, die Entwickler-Tools anbieten, einen relativ kleinen Prozentsatz aller Software-Unternehmen ausmachen, und deshalb ist es schwieriger, eine solche Gelegenheit zu finden.
Mihail Vladov
Eine separate Frage. Warum rufst du hier nicht VerifyAll an? Gibt es eine Möglichkeit, JustMock anzuweisen, zu überprüfen, ob auch die lokale Funktion aufgerufen wurde?
John Zabroski
2
Hallo @JohnZabroski, für das getestete Szenario mussten keine Vorkommen bestätigt werden. Natürlich können Sie überprüfen, ob ein Anruf getätigt wurde. Zunächst müssen Sie angeben, wie oft die Methode aufgerufen werden soll. So: .DoNothing().OccursOnce();Und behaupten Sie später, dass der Aufruf durch Aufrufen der Mock.Assert(foo);Methode erfolgt ist. Wenn Sie interessiert sind, wie andere Szenarien unterstützt werden, lesen Sie unseren Hilfeartikel Asserting Occurrence .
Mihail Vladov
0

Ich verwende Inline-Funktionen, um den Druck der Speicherbereinigung zu vermeiden, insbesondere wenn es um länger laufende Methoden geht. Angenommen, man möchte 2 Jahre oder Marktdaten für ein bestimmtes Tickersymbol erhalten. Außerdem kann man bei Bedarf eine Menge Funktionalität und Geschäftslogik packen.

Sie öffnen eine Socket-Verbindung zum Server und durchlaufen die Daten, die ein Ereignis an ein Ereignis binden. Man kann es sich genauso vorstellen, wie eine Klasse entworfen wurde, nur schreibt man nicht überall Hilfsmethoden, die wirklich nur für ein Stück Funktionalität funktionieren. Im Folgenden finden Sie ein Beispiel dafür, wie dies aussehen könnte. Bitte beachten Sie, dass ich Variablen verwende und die "Helfer" -Methoden unter dem Endlich stehen. Im Schließlich entferne ich die Ereignishandler, wenn meine Exchange-Klasse extern / injiziert wäre, hätte ich keinen ausstehenden Ereignishandler registriert

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

Sie können die Vorteile wie unten erwähnt sehen, hier sehen Sie eine Beispielimplementierung. Hoffe, das hilft, die Vorteile zu erklären.

Walter Vehoeven
quelle
2
1. Das ist ein wirklich komplexes Beispiel und eine Erklärung, um nur lokale Funktionen zu demonstrieren. 2. Lokale Funktionen vermeiden im Vergleich zu Lambdas in diesem Beispiel keine Zuordnungen, da sie noch in Delegaten konvertiert werden müssen. Ich sehe also nicht ein, wie sie GC vermeiden würden.
Svick
1
Svicks Antwort deckt den Rest wirklich gut ab, ohne Variablen weiterzugeben / zu kopieren. Keine Notwendigkeit, seine Antwort zu duplizieren
Walter Vehoeven