ローカル関数とLambda C#7.0


178

C#7.0の新しい実装を見ていて、ローカル関数が実装されているのは興味深いのですが、ローカル関数がラムダ式よりも優先されるシナリオと、その2つの違いは想像できません。

ラムダはanonymous関数ですがローカル関数はそうでないことを理解していますが、ローカル関数がラムダ式よりも優れている実際のシナリオを理解できません

どんな例でも大歓迎です。ありがとう。


9
ジェネリック、出力パラメーター、ラムダをnullに初期化する必要のない再帰関数など
Kirk Woll

5
@KirkWoll-これを回答として投稿してください。
Enigmativity 2016

回答:


276

これは、ローカル関数が最初に議論されたC#設計会議ノートでMads Torgersenによって説明されました

ヘルパー関数が必要です。単一の関数内からのみ使用しており、その関数のスコープ内にある変数と型パラメーターを使用している可能性があります。一方、ラムダとは異なり、ファーストクラスオブジェクトとして必要ないため、デリゲート型を指定して実際のデリゲートオブジェクトを割り当てる必要はありません。また、再帰的またはジェネリックにしたり、イテレータとして実装したりすることもできます。

さらに拡張すると、次のような利点があります。

  1. パフォーマンス。

    ラムダを作成するときは、デリゲートを作成する必要があります。これは、この場合は不要な割り当てです。ローカル関数は実際には単なる関数であり、デリゲートは必要ありません。

    また、ローカル関数はローカル変数をキャプチャする方が効率的です。ラムダは通常、変数をクラスにキャプチャしますが、ローカル関数はstruct(を使用して渡されるref)を使用できるため、割り当てを回避できます。

    これは、ローカル関数の呼び出しが安価でインライン化できるため、パフォーマンスがさらに向上する可能性があることも意味します。

  2. ローカル関数は再帰的です。

    nullラムダも再帰的にすることができますが、まずデリゲート変数に割り当て、次にラムダに割り当てる厄介なコードが必要です。ローカル関数は自然に再帰的です(相互再帰を含む)。

  3. ローカル関数はジェネリックにすることができます。

    ラムダは具象型の変数に割り当てる必要があるため、ジェネリックにすることはできません(その型は外部スコープのジェネリック変数を使用できますが、同じことではありません)。

  4. ローカル関数はイテレータとして実装できます。

    ラムダはyield return(およびyield break)キーワードを使用してIEnumerable<T>-returning関数を実装することはできません。ローカル関数はできます。

  5. ローカル関数がよく見えます。

    これは上記の引用では言及されておらず、私の個人的な偏見かもしれませんが、デリゲート変数にラムダを割り当てるよりも、通常の関数構文の方が見栄えが良いと思います。ローカル関数もより簡潔です。

    比較:

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

22
ローカル関数に呼び出し側のパラメーター名があることを追加したいと思います。ラムダにはありません。
Lensflare 2017

3
@Lensflareラムダのパラメーター名が保持されないのは事実ですが、それは、それらが独自の名前を持つデリゲートに変換する必要があるためです。例:Func<int, int, int> f = (x, y) => x + y; f(arg1:1, arg2:1);
2017

1
素晴らしいリスト!ただし、IL / JITコンパイラーが、デリゲートについても1。
Marcin Kaczmarek、2018

1
@Casebashラムダは常にデリゲートを使用し、そのデリゲートはクロージャをとして保持するためobject。したがって、ラムダは構造体を使用できますが、ボックス化する必要があるため、追加の割り当てが必要になります。
2018年

1
@happybitsほとんどの場合、メソッドに渡す場合など、名前を付ける必要がない場合。
スビック

83

svickのすばらしい答えに加えて、ローカル関数にはもう1つの利点があります。
それらは、returnステートメントの後でも、関数のどこにでも定義できます。

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
すべてのヘルパー関数を関数#region Helpersの下部に配置することに慣れているので、これは本当に便利です。そのため、その関数内での混乱を避け、特にメインクラスでの混乱を避けることができます。
AustinWBryan 2018

これもありがたいです。見始める必要がないので、見ている主な機能が読みやすくなります。実装の詳細を確認したい場合は、最後まで見てください。
Remi Despres-Smyth

3
関数が大きすぎて、その中に領域が必要な場合は、大きすぎます。
ssmith

9

ローカル関数をテストする方法についても疑問がある場合は、JustMockにそれを実行する機能があるため、JustMockをチェックする必要があります。以下は、テストされる簡単なクラスの例です。

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

そして、これはテストがどのように見えるかです:

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

これがJustMockのドキュメントへのリンクです。

免責事項。私はJustMockを担当する開発者の一人です。


このような情熱的な開発者がツールを使用することを人々に勧めているのは素晴らしいことです。どのようにしてフルタイムの仕事として開発者ツールを書くことに興味を持ったのですか?アメリカ人としての私の印象は、修士号または博士号を持っていない限り、そのようなキャリアを見つけるのは難しいということです。コンプサイエンス。
John Zabroski、

こんにちはジョン、親切な言葉をありがとう。ソフトウェア開発者として、私がクライアントに提供する価値についてクライアントから高く評価される以上のことはないと思います。それを挑戦的で競争力のある仕事への欲望と組み合わせると、私が情熱を注ぐことになるもののかなり限定されたリストを受け取ることになります。そのリストには、生産性向上ツールの記述があります。少なくとも私の心の中で:)キャリアに関しては、開発ツールを提供している会社はすべてのソフトウェア会社のかなりの割合であると私は思います、そしてそれがそのような機会を見つけることがより難しい理由です。
Mihail Vladov

1つの個別の質問。ここでVerifyAllを呼び出さないのですか?JustMockにローカル関数が呼び出されたことを確認するように指示する方法はありますか?
John Zabroski、

2
こんにちは@JohnZabroskiです。テスト済みのシナリオでは、オカレンスをアサートする必要はありませんでした。もちろん、呼び出しが行われたことを確認できます。最初に、メソッドが呼び出されると予想される回数を指定する必要があります。このように:.DoNothing().OccursOnce();後でMock.Assert(foo);メソッドを呼び出すことによって呼び出しが行われたことをアサートします。他のシナリオがどのようにサポートされているかに興味がある場合は、アサーティングオカレンスのヘルプ記事をご覧ください。
Mihail Vladov

0

実行時間の長いメソッドを処理する場合は、インライン関数を使用して、ガベージコレクションのプレッシャーを特に回避します。特定のティッカーシンボルの2年または市場データを取得したいとします。また、必要に応じて、多くの機能とビジネスロジックをパックできます。

1つは、サーバーへのソケット接続を開き、イベントをイベントにバインドするデータをループすることです。クラスを設計するのと同じように考えることができます。実際に機能しているヘルパーメソッドが1か所だけで機能しているところにヘルパーメソッドを作成しているのは1人だけではありません。以下はこれがどのように見えるかのサンプルです、私は変数を使用しており、「ヘルパー」メソッドは最終的に下にあることに注意してください。最後に、イベントハンドラーをうまく削除します。Exchangeクラスが外部/挿入される場合、保留中のイベントハンドラーを登録しません。

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

以下に示すように、利点を確認できます。ここでは、サンプル実装を確認できます。それが利点の説明に役立つことを願っています。


2
1.これは、ローカル関数を示すためだけの非常に複雑な例と説明です。2.ローカル関数は、この例のラムダと比較すると、割り当てを回避しません。デリゲートに変換する必要があるためです。したがって、彼らがどのようにGCを回避するかはわかりません。
2018

1
/ copying変数を渡さないで、svickの答えは残りを本当にうまくカバーします。彼の答えを複製する必要はありません
Walter Vehoeven '
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.