Pertanyaan Mengapa Anda menggunakan Ekspresi > daripada Func ?


Saya mengerti lambdas dan Func dan Action delegasi. Tapi ekspresi membuatku tegang. Dalam situasi apa Anda akan menggunakan Expression<Func<T>> daripada orang tua yang polos Func<T>?


771
2018-04-27 13:50


asal


Jawaban:


Ketika Anda ingin memperlakukan ekspresi lambda sebagai pohon ekspresi dan melihat ke dalam mereka daripada mengeksekusi mereka. Sebagai contoh, LINQ to SQL mendapatkan ekspresi dan mengubahnya menjadi pernyataan SQL yang setara dan mengirimkannya ke server (daripada mengeksekusi lambda).

Secara konseptual, Expression<Func<T>> aku s benar-benar berbeda dari Func<T>. Func<T> menandakan a delegate yang cukup banyak penunjuk ke metode dan Expression<Func<T>> menandakan a struktur data pohon untuk ekspresi lambda. Struktur pohon ini menggambarkan apa yang dilakukan ekspresi lambda daripada melakukan hal yang sebenarnya. Pada dasarnya ini menyimpan data tentang komposisi ekspresi, variabel, pemanggilan metode, ... (misalnya menyimpan informasi seperti lambda ini adalah beberapa parameter konstan + beberapa). Anda dapat menggunakan deskripsi ini untuk mengonversinya menjadi metode yang sebenarnya (dengan Expression.Compile) atau melakukan hal-hal lain (seperti contoh LINQ to SQL) dengannya. Tindakan memperlakukan lambdas sebagai metode anonim dan pohon ekspresi adalah murni waktu kompilasi.

Func<int> myFunc = () => 10; // similar to: int myAnonMethod() { return 10; }

secara efektif akan mengkompilasi ke metode IL yang tidak mendapatkan apa-apa dan mengembalikan 10.

Expression<Func<int>> myExpression = () => 10;

akan dikonversi ke struktur data yang menggambarkan ekspresi yang tidak mendapat parameter dan mengembalikan nilai 10:

Expression vs Func  gambar lebih besar

Sementara keduanya terlihat sama pada waktu kompilasi, apa yang dihasilkan oleh compiler benar-benar berbeda.


956
2018-04-27 13:52



Saya menambahkan jawaban-untuk-noobs karena jawaban ini tampak di atas kepala saya, sampai saya menyadari betapa sederhananya itu. Terkadang adalah harapan Anda bahwa itu rumit yang membuat Anda tidak dapat 'membungkus kepala Anda di sekitarnya'.

Saya tidak perlu memahami perbedaannya sampai saya masuk ke 'bug' yang benar-benar mengganggu untuk mencoba menggunakan LINQ-to-SQL secara umum:

public IEnumerable<T> Get(Func<T, bool> conditionLambda){
  using(var db = new DbContext()){
    return db.Set<T>.Where(conditionLambda);
  }
}

Ini bekerja dengan baik sampai saya mulai mendapatkan OutofMemoryExceptions pada dataset yang lebih besar. Menetapkan breakpoint di dalam lambda membuat saya menyadari bahwa iterasi melalui setiap baris di meja saya satu demi satu mencari pertandingan dengan kondisi lambda saya. Ini membuat saya bingung untuk sementara waktu, karena mengapa sih memperlakukan tabel data saya sebagai raksasa IEnumerable daripada melakukan LINQ-to-SQL seperti yang seharusnya? Itu juga melakukan hal yang sama di mitra LINQ-to-MongoDb saya.

Perbaikan itu hanya berubah Func<T, bool> ke Expression<Func<T, bool>>, jadi saya mencari tahu mengapa itu membutuhkan Expression dari pada Func, berakhir di sini.

Ekspresi hanya mengubah seorang delegasi menjadi data tentang dirinya sendiri. Begitu a => a + 1 menjadi sesuatu seperti "Di sisi kiri ada sebuah int a. Di sisi kanan Anda menambahkan 1 ke sana. " Itu dia. Kamu bisa pulang sekarang. Ini jelas lebih terstruktur dari itu, tapi itu pada dasarnya semua pohon ekspresi benar-benar - tidak ada yang membungkus kepalamu.

Memahami itu, menjadi jelas mengapa LINQ-to-SQL membutuhkan Expression, dan a Func tidak memadai. Func tidak membawa dengan cara untuk masuk ke dirinya sendiri, untuk melihat seluk-beluk bagaimana menerjemahkannya ke dalam SQL / MongoDb / query lainnya. Anda tidak dapat melihat apakah itu melakukan penambahan atau perkalian pada pengurangan. Yang bisa Anda lakukan hanyalah menjalankannya. Expression, di sisi lain, memungkinkan Anda untuk melihat ke dalam delegasi dan melihat semua yang ingin dilakukan, memberdayakan Anda untuk menerjemahkannya menjadi apa pun yang Anda inginkan, seperti kueri SQL. Func tidak berhasil karena DbContext saya buta terhadap apa yang sebenarnya dalam ekspresi lambda untuk mengubahnya menjadi SQL, sehingga hal terbaik berikutnya dan iterasi yang bersyarat melalui setiap baris di meja saya.

Edit: menjelaskan kalimat terakhir saya atas permintaan John Peter:

IQueryable memanjang IEnumerable, jadi metode IEnumerable seperti Where() dapatkan overload yang diterima Expression. Ketika Anda melewati Expression untuk itu, Anda menyimpan IQueryable sebagai hasilnya, tetapi ketika Anda lulus Func, Anda jatuh kembali ke basis IEnumerable dan Anda akan mendapatkan IEnumerable sebagai hasilnya. Dengan kata lain, tanpa menyadari Anda telah mengubah dataset Anda menjadi daftar yang akan di-iterasikan sebagai lawan dari sesuatu yang harus ditanyakan. Sulit untuk melihat perbedaan sampai Anda benar-benar melihat di bawah kap di tanda tangan.


196
2018-01-05 08:04



Pertimbangan yang sangat penting dalam pilihan Ekspresi vs Func adalah bahwa penyedia IQueryable seperti LINQ untuk Entitas dapat 'mencerna' apa yang Anda lulus dalam suatu Ekspresi, tetapi akan mengabaikan apa yang Anda lulus dalam Func. Saya memiliki dua posting blog tentang hal ini:

Lebih lanjut tentang Ekspresi vs Func dengan Kerangka Entitas dan Jatuh cinta dengan LINQ - Bagian 7: Ekspresi dan Funcs (bagian terakhir)


91
2018-01-11 15:57



Saya ingin menambahkan beberapa catatan tentang perbedaan antara Func<T> dan Expression<Func<T>>:

  • Func<T> hanyalah multicastDelegate lama sekolah biasa;
  • Expression<Func<T>> merupakan representasi ekspresi lambda dalam bentuk pohon ekspresi;
  • pohon ekspresi dapat dibangun melalui sintaks ekspresi lambda atau melalui sintaks API;
  • pohon ekspresi dapat dikompilasi ke delegasi Func<T>;
  • konversi terbalik secara teoritis mungkin, tapi itu semacam pembusukan, tidak ada fungsi bawaan untuk itu karena itu bukan proses yang langsung;
  • pohon ekspresi dapat diamati / diterjemahkan / diubah melalui ExpressionVisitor;
  • metode ekstensi untuk IEnumerable beroperasi dengan Func<T>;
  • metode perluasan untuk IQuerable beroperasi dengan Expression<Func<T>>.

Ada artikel yang menjelaskan detail dengan contoh kode:
LINQ: Func <T> vs. Ekspresi <Func <T >>.

Semoga itu akan membantu.


60
2018-06-11 00:02



Ada penjelasan yang lebih filosofis tentangnya dari buku Krzysztof Cwalina (Kerangka Desain Kerangka Kerja: Konvensi, Idiom, dan Pola untuk Perpustakaan Reusable .NET);

Rico Mariani

Edit untuk versi non-gambar:

Sering kali Anda akan menginginkannya Func atau Tindakan jika semua yang perlu terjadi adalah menjalankan beberapa kode. Anda butuh Ekspresi ketika kode perlu dianalisis, diserialisasikan, atau dioptimalkan sebelum dijalankan. Ekspresi adalah untuk berpikir tentang kode, Func / Action adalah untuk menjalankannya.


40
2018-03-11 13:10



LINQ adalah contoh kanonik (misalnya, berbicara dengan database), tetapi sebenarnya, setiap kali Anda lebih peduli tentang mengekspresikan apa lakukan, daripada benar-benar melakukannya. Sebagai contoh, saya menggunakan pendekatan ini dalam tumpukan RPC protobuf-net (untuk menghindari kode-generasi dll) - sehingga Anda memanggil metode dengan:

string result = client.Invoke(svc => svc.SomeMethod(arg1, arg2, ...));

Ini mendekonstruksi pohon ekspresi untuk menyelesaikan SomeMethod (dan nilai setiap argumen), melakukan panggilan RPC, memperbarui apa pun ref/out args, dan mengembalikan hasil dari panggilan jarak jauh. Ini hanya mungkin melalui pohon ekspresi. Saya tutup ini lagi sini.

Contoh lain adalah ketika Anda membangun pohon ekspresi secara manual untuk tujuan kompilasi ke lambda, seperti yang dilakukan oleh operator umum kode.


33
2018-04-27 14:13



Anda akan menggunakan ekspresi ketika Anda ingin memperlakukan fungsi Anda sebagai data dan bukan sebagai kode. Anda dapat melakukan ini jika Anda ingin memanipulasi kode (sebagai data). Sebagian besar waktu jika Anda tidak melihat perlunya ekspresi maka Anda mungkin tidak perlu menggunakannya.


18
2018-04-27 13:53



Alasan utamanya adalah ketika Anda tidak ingin menjalankan kode secara langsung, tetapi ingin memeriksanya. Ini bisa untuk sejumlah alasan:

  • Memetakan kode ke lingkungan yang berbeda (mis. Kode C # ke SQL dalam Entity Framework)
  • Mengganti bagian kode dalam runtime (pemrograman dinamis atau bahkan teknik DRY biasa)
  • Validasi kode (sangat berguna saat meniru skrip atau saat melakukan analisis)
  • Serialisasi - ekspresi dapat diserialkan dengan mudah dan aman, delegasi tidak bisa
  • Keamanan yang sangat diketikkan pada hal-hal yang pada dasarnya tidak diketik-kuat, dan mengeksploitasi pemeriksaan kompiler meskipun Anda melakukan panggilan dinamis dalam waktu proses (ASP.NET MVC 5 dengan Razor adalah contoh yang bagus)

15
2018-03-26 12:54



Saya belum melihat jawaban apa pun yang menyebutkan kinerja. Lewat Func<>s ke Where() atau Count() buruk. Sangat buruk. Jika Anda menggunakan Func<> maka itu disebut IEnumerable Item LINQ, bukan IQueryable, yang berarti seluruh tabel ditarik masuk dan kemudian tersaring. Expression<Func<>> secara signifikan lebih cepat, terutama jika Anda meng-query database yang menjalankan server lain.


5
2018-06-16 15:58