Fungsi lokal vs Lambda C # 7.0

178

Saya melihat implementasi baru di C # 7.0 dan saya merasa menarik bahwa mereka telah mengimplementasikan fungsi lokal tetapi saya tidak bisa membayangkan skenario di mana fungsi lokal lebih disukai daripada ekspresi lambda, dan apa perbedaan antara keduanya.

Saya mengerti bahwa lambda adalah anonymousfungsi sedangkan fungsi lokal tidak, tetapi saya tidak bisa menemukan skenario dunia nyata, di mana fungsi lokal memiliki kelebihan dibandingkan dengan ekspresi lambda

Contoh apa pun akan sangat dihargai. Terima kasih.

Sid
sumber
9
Generik, parameter out, fungsi rekursif tanpa harus menginisialisasi lambda menjadi null, dll.
Kirk Woll
5
@KirkWoll - Anda harus memposting ini sebagai jawaban.
Enigmativity

Jawaban:

276

Ini dijelaskan oleh Mads Torgersen dalam Catatan Rapat Desain C # di mana fungsi-fungsi lokal pertama kali dibahas :

Anda menginginkan fungsi pembantu. Anda hanya menggunakannya dari dalam satu fungsi, dan kemungkinan menggunakan variabel dan tipe parameter yang ada di dalam lingkup yang berisi fungsi. Di sisi lain, tidak seperti lambda Anda tidak memerlukannya sebagai objek kelas satu, jadi Anda tidak peduli untuk memberikannya tipe delegasi dan mengalokasikan objek delegasi yang sebenarnya. Anda juga mungkin ingin menjadi rekursif atau generik, atau menerapkannya sebagai iterator.

Untuk mengembangkannya lagi, keuntungannya adalah:

  1. Performa.

    Saat membuat lambda, delegasi harus dibuat, yang merupakan alokasi yang tidak perlu dalam kasus ini. Fungsi lokal benar-benar hanya fungsi, tidak ada delegasi yang diperlukan.

    Juga, fungsi lokal lebih efisien dengan menangkap variabel lokal: lambdas biasanya menangkap variabel ke dalam kelas, sementara fungsi lokal dapat menggunakan struct (lulus menggunakan ref), yang lagi-lagi menghindari alokasi.

    Ini juga berarti memanggil fungsi lokal lebih murah dan dapat digarisbawahi, mungkin meningkatkan kinerja lebih jauh.

  2. Fungsi lokal dapat bersifat rekursif.

    Lambdas juga bisa bersifat rekursif, tetapi membutuhkan kode yang canggung, di mana Anda pertama kali menetapkan nullvariabel delegasi dan kemudian lambda. Fungsi lokal secara alami dapat bersifat rekursif (termasuk saling recursive).

  3. Fungsi lokal bisa bersifat generik.

    Lambdas tidak bisa generik, karena mereka harus ditugaskan ke variabel dengan tipe konkret (tipe itu dapat menggunakan variabel generik dari lingkup luar, tapi itu bukan hal yang sama).

  4. Fungsi lokal dapat diimplementasikan sebagai iterator.

    Lambdas tidak dapat menggunakan kata kunci yield return(dan yield break) untuk mengimplementasikan IEnumerable<T>fungsi -urnurning. Fungsi lokal bisa.

  5. Fungsi lokal terlihat lebih baik.

    Ini tidak disebutkan dalam kutipan di atas dan mungkin hanya bias pribadi saya, tapi saya pikir sintaks fungsi normal terlihat lebih baik daripada menugaskan lambda ke variabel delegasi. Fungsi lokal juga lebih ringkas.

    Membandingkan:

    int add(int x, int y) => x + y;
    Func<int, int, int> add = (x, y) => x + y;
svick
sumber
22
Saya ingin menambahkan bahwa fungsi lokal memiliki nama parameter di sisi pemanggil. Lambda tidak.
Lensflare
3
@Lensflare Memang benar bahwa nama parameter lambdas tidak dipertahankan, tetapi itu karena mereka harus dikonversi menjadi delegasi, yang memiliki nama mereka sendiri. Sebagai contoh: Func<int, int, int> f = (x, y) => x + y; f(arg1:1, arg2:1);.
svick
1
Daftar hebat! Namun, saya bisa membayangkan bagaimana kompiler IL / JIT dapat melakukan semua optimasi yang disebutkan dalam 1. juga untuk delegasi jika penggunaannya mematuhi aturan-aturan tertentu.
Marcin Kaczmarek
1
@Casebash Karena lambdas selalu menggunakan delegasi dan delegasi itu memegang penutupan sebagai object. Jadi, lambdas bisa menggunakan struct, tetapi harus dikotak, sehingga Anda masih memiliki alokasi tambahan.
svick
1
@ Happybits Sebagian besar ketika Anda tidak perlu memberikan nama untuk itu, seperti ketika Anda meneruskannya ke metode.
svick
83

Selain jawaban hebat svick, ada satu lagi keuntungan fungsi lokal:
Mereka dapat didefinisikan di mana saja dalam fungsi, bahkan setelah returnpernyataan.

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
sumber
5
Ini sangat berguna, karena saya dapat membiasakan diri menempatkan semua fungsi helper #region Helpersdi bagian bawah fungsi, sehingga untuk menghindari kekacauan di dalam fungsi itu dan terutama menghindari kekacauan di kelas utama.
AustinWBryan
Saya juga menghargai ini. Itu membuat fungsi utama yang Anda lihat lebih mudah dibaca, karena Anda tidak perlu melihat-lihat untuk menemukan di mana ia dimulai. Jika Anda ingin melihat detail implementasi, terus melihat melewati akhir.
Remi Despres-Smyth
3
jika fungsi Anda sangat besar mereka membutuhkan daerah di dalamnya, mereka terlalu besar.
ssmith
9

Jika Anda juga bertanya-tanya bagaimana cara menguji fungsi lokal Anda harus memeriksa JustMock karena memiliki fungsi untuk melakukannya. Berikut adalah contoh kelas sederhana yang akan diuji:

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

Dan inilah bagaimana tes ini terlihat:

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

Berikut ini tautan ke dokumentasi JustMock .

Penolakan. Saya adalah salah satu pengembang yang bertanggung jawab untuk JustMock .

Mihail Vladov
sumber
senang melihat pengembang yang bersemangat mengadvokasi orang untuk menggunakan alat mereka. Bagaimana Anda tertarik untuk menulis alat pengembang sebagai pekerjaan penuh waktu? Sebagai orang Amerika, kesan saya adalah sulit untuk menemukan karier seperti itu kecuali Anda memiliki gelar master atau Ph.D. dalam comp sci.
John Zabroski
Hai John dan terima kasih atas kata-kata baiknya. Sebagai pengembang perangkat lunak, saya tidak melihat sesuatu yang lebih baik daripada dihargai oleh klien saya untuk nilai yang saya berikan untuk mereka. Kombinasikan itu dengan keinginan untuk pekerjaan yang menantang dan kompetitif dan Anda akan menerima daftar hal-hal yang cukup terbatas yang akan saya sukai. Perangkat pengembang menulis produktivitas ada di daftar itu. Setidaknya dalam ingatan saya :) Mengenai karir, saya pikir perusahaan yang menyediakan pengembang perangkat adalah persentase yang sangat kecil dari semua perusahaan perangkat lunak dan inilah mengapa lebih sulit untuk menemukan peluang seperti itu.
Mihail Vladov
Satu pertanyaan terpisah. Mengapa Anda tidak menghubungi VerifyAll di sini? Apakah ada cara untuk memberitahu JustMock untuk memverifikasi fungsi lokal juga dipanggil?
John Zabroski
2
Hai @JohnZabroski, skenario yang diuji tidak memerlukan kejadian yang tegas. Tentu saja, Anda dapat memverifikasi bahwa panggilan telah dilakukan. Pertama, Anda perlu menentukan berapa kali Anda mengharapkan metode dipanggil. Seperti ini: .DoNothing().OccursOnce();Dan kemudian menegaskan bahwa panggilan itu dilakukan dengan memanggil Mock.Assert(foo);metode. Jika Anda tertarik bagaimana skenario lain didukung, Anda bisa membaca artikel bantuan Penegasan Kejadian kami .
Mihail Vladov
0

Saya menggunakan fungsi sebaris untuk menghindari tekanan pengumpulan sampah secara khusus ketika berhadapan dengan metode yang berjalan lebih lama. Katakanlah seseorang ingin mendapatkan 2 tahun atau memasarkan data untuk simbol ticker yang diberikan. Juga, seseorang dapat mengemas banyak fungsionalitas dan logika bisnis jika diperlukan.

apa yang dilakukan adalah membuka koneksi soket ke server dan mengulangi data yang mengikat suatu peristiwa ke suatu peristiwa. Seseorang dapat memikirkannya dengan cara yang sama seperti kelas dirancang, hanya satu yang tidak menulis metode pembantu di semua tempat yang benar-benar hanya berfungsi untuk satu fungsi saja. di bawah ini adalah beberapa contoh bagaimana ini terlihat, harap dicatat bahwa saya menggunakan variabel dan metode "pembantu" di bawah yang terakhir. Pada akhirnya saya dengan baik menghapus event handler, jika kelas Exchange saya akan eksternal / disuntikkan saya tidak akan memiliki pendaftar event pending yang terdaftar

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

Anda dapat melihat keuntungan seperti yang disebutkan di bawah ini, di sini Anda dapat melihat contoh implementasi. Harapan itu membantu menjelaskan manfaatnya.

Walter Vehoeven
sumber
2
1. Itu adalah contoh dan penjelasan yang sangat kompleks hanya untuk menunjukkan fungsi lokal. 2. Fungsi lokal tidak menghindari alokasi apa pun jika dibandingkan dengan lambdas dalam contoh ini, karena mereka masih harus dikonversi ke delegasi. Jadi saya tidak melihat bagaimana mereka akan menghindari GC.
svick
1
tidak lewat / menyalin variabel sekitar, jawaban svick mencakup sisanya dengan sangat baik. Tidak perlu menduplikasi jawabannya
Walter Vehoeven