Saya memahami lambdas dan Func
dan Action
delegasi. Tapi ekspresi membuatku bingung.
Dalam situasi apa Anda akan menggunakan yang lama Expression<Func<T>>
dan tidak biasa Func<T>
?
Saya memahami lambdas dan Func
dan Action
delegasi. Tapi ekspresi membuatku bingung.
Dalam situasi apa Anda akan menggunakan yang lama Expression<Func<T>>
dan tidak biasa Func<T>
?
Jawaban:
Saat Anda ingin memperlakukan ekspresi lambda sebagai pohon ekspresi dan lihat ke dalamnya alih-alih menjalankannya. Sebagai contoh, LINQ ke SQL mendapatkan ekspresi dan mengubahnya ke pernyataan SQL yang setara dan mengirimkannya ke server (daripada mengeksekusi lambda).
Secara konseptual, Expression<Func<T>>
sama sekali berbeda dari Func<T>
. Func<T>
menunjukkan suatu delegate
yang cukup banyak penunjuk ke metode dan Expression<Func<T>>
menunjukkan struktur data pohon untuk ekspresi lambda. Struktur pohon ini menggambarkan apa yang dilakukan ekspresi lambda daripada melakukan hal yang sebenarnya. Ini pada dasarnya menyimpan data tentang komposisi ekspresi, variabel, pemanggilan metode, ... (misalnya ia menyimpan informasi seperti lambda ini adalah beberapa konstanta + beberapa parameter). Anda dapat menggunakan deskripsi ini untuk mengubahnya menjadi metode aktual (dengan Expression.Compile
) atau melakukan hal-hal lain (seperti contoh LINQ ke SQL) dengannya. Tindakan memperlakukan lambda sebagai metode anonim dan pohon ekspresi adalah murni waktu kompilasi.
Func<int> myFunc = () => 10; // similar to: int myAnonMethod() { return 10; }
akan secara efektif mengkompilasi ke metode IL yang tidak mendapatkan apa pun dan mengembalikan 10.
Expression<Func<int>> myExpression = () => 10;
akan dikonversi ke struktur data yang menggambarkan ekspresi yang tidak memiliki parameter dan mengembalikan nilai 10:
Walaupun keduanya terlihat sama pada waktu kompilasi, apa yang dihasilkan oleh kompiler sama sekali berbeda .
Expression
berisi informasi meta tentang delegasi tertentu.
Expression<Func<...>>
bukan hanya Func<...>
.
(isAnExample) => { if(isAnExample) ok(); else expandAnswer(); }
ekspresi seperti itu adalah ExpressionTree, cabang dibuat untuk pernyataan-If.
Saya menambahkan jawaban untuk noobs karena jawaban ini tampak di atas kepala saya, sampai saya menyadari betapa sederhananya itu. Kadang-kadang harapan Anda bahwa itu rumit yang membuat Anda tidak dapat 'membungkus kepala Anda'.
Saya tidak perlu memahami perbedaannya sampai saya menemukan 'bug' yang benar-benar mengganggu yang 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 kumpulan data yang lebih besar. Menetapkan breakpoint di dalam lambda membuat saya menyadari bahwa itu berulang melalui setiap baris di meja saya satu-per-satu mencari korek api dengan kondisi lambda saya. Ini mengejutkan saya untuk sementara waktu, karena mengapa itu memperlakukan tabel data saya sebagai IEnumerable raksasa daripada melakukan LINQ-to-SQL seperti yang seharusnya? Itu juga melakukan hal yang sama persis di rekan LINQ-to-MongoDb saya.
Cara mengatasinya adalah hanya untuk mengubah Func<T, bool>
ke dalam Expression<Func<T, bool>>
, jadi saya googled mengapa membutuhkan Expression
bukan Func
, berakhir di sini.
Ekspresi hanya mengubah delegasi menjadi data tentang dirinya sendiri. Jadi a => a + 1
menjadi sesuatu seperti "Di sisi kiri ada 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 sebenarnya - tidak ada yang membungkus kepala Anda.
Memahami itu, menjadi jelas mengapa LINQ-to-SQL membutuhkan Expression
, dan Func
tidak memadai. Func
tidak membawa dengannya cara untuk masuk ke dirinya sendiri, untuk melihat seluk-beluk bagaimana menerjemahkannya ke dalam SQL / MongoDb / permintaan lainnya. Anda tidak dapat melihat apakah itu melakukan penambahan atau penggandaan atau pengurangan. Yang bisa Anda lakukan adalah menjalankannya. Expression
, di sisi lain, memungkinkan Anda untuk melihat ke dalam delegasi dan melihat semua yang ingin dilakukan. Ini memberdayakan Anda untuk menerjemahkan delegasi ke dalam apa pun yang Anda inginkan, seperti kueri SQL. Func
tidak berfungsi karena DbContext saya buta terhadap isi ekspresi lambda. Karena itu, tidak bisa mengubah ekspresi lambda menjadi SQL; Namun, ia melakukan hal terbaik berikutnya dan mengulanginya dengan syarat melalui setiap baris di meja saya.
Sunting: menguraikan tentang kalimat terakhir saya atas permintaan John Peter:
IQueryable memperluas IEnumerable, jadi metode IEnumerable seperti Where()
mendapatkan kelebihan beban yang menerima Expression
. Ketika Anda lulus suatu Expression
untuk itu, Anda menyimpan IQueryable sebagai hasilnya, tetapi ketika Anda lulus Func
, Anda jatuh kembali pada basis IEnumerable dan Anda akan mendapatkan IEnumerable sebagai hasilnya. Dengan kata lain, tanpa memperhatikan Anda telah mengubah dataset Anda menjadi daftar untuk diiterasi sebagai lawan dari sesuatu untuk ditanyakan. Sulit untuk melihat perbedaan sampai Anda benar-benar melihat di bawah kap di tanda tangan.
Pertimbangan yang sangat penting dalam pilihan Ekspresi vs Func adalah bahwa penyedia IQueryable seperti LINQ ke Entitas dapat 'mencerna' apa yang Anda berikan dalam Ekspresi, tetapi akan mengabaikan apa yang Anda lewati dalam Func. Saya memiliki dua posting blog tentang hal ini:
Lebih lanjut tentang Ekspresi vs Fungsi dengan Kerangka Entitas dan Jatuh Cinta dengan LINQ - Bagian 7: Ekspresi dan Fungsi (bagian terakhir)
Saya ingin menambahkan beberapa catatan tentang perbedaan antara Func<T>
dan Expression<Func<T>>
:
Func<T>
hanyalah MulticastDelegate sekolah tua yang normal;Expression<Func<T>>
adalah representasi ekspresi lambda dalam bentuk pohon ekspresi;Func<T>
;ExpressionVisitor
;Func<T>
;Expression<Func<T>>
.Ada artikel yang menjelaskan detail dengan contoh kode:
LINQ: Func <T> vs Expression <Func <T>> .
Semoga bermanfaat.
Ada penjelasan yang lebih filosofis tentang hal itu dari buku Krzysztof Cwalina ( Kerangka Desain Pedoman: Konvensi, Idiom, dan Pola untuk Reusable .NET Libraries );
Edit untuk versi non-gambar:
Sering kali Anda akan menginginkan Fungsi atau Aksi jika semua yang perlu terjadi adalah menjalankan beberapa kode. Anda perlu Ekspresi ketika kode perlu dianalisis, diserialisasi, atau dioptimalkan sebelum dijalankan. Ekspresi adalah untuk memikirkan kode, Fungsi / Aksi adalah untuk menjalankannya.
database.data.Where(i => i.Id > 0)
dieksekusi sebagai SELECT FROM [data] WHERE [id] > 0
. Jika Anda hanya lulus dalam Func, Anda telah menempatkan penutup mata pada driver Anda dan semua itu dapat dilakukan adalah SELECT *
dan kemudian setelah itu dimuat semua data ke dalam memori, iterate melalui masing-masing dan menyaring segala sesuatu dengan id> 0. Wrapping Anda Func
di Expression
memberdayakan driver untuk menganalisis Func
dan mengubahnya menjadi permintaan Sql / MongoDb / lainnya.
Expression
tetapi ketika saya sedang berlibur akan Func/Action
;)
LINQ adalah contoh kanonik (misalnya, berbicara ke database), tetapi sebenarnya, setiap kali Anda lebih peduli untuk mengungkapkan apa yang harus dilakukan, daripada benar-benar melakukannya. Sebagai contoh, saya menggunakan pendekatan ini di tumpukan RPC protobuf-net (untuk menghindari pembuatan kode dll) - jadi 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 setiap ref
/ out
argumen, dan mengembalikan hasilnya dari panggilan jarak jauh. Ini hanya mungkin melalui pohon ekspresi. Saya membahas hal ini lebih lanjut di sini .
Contoh lain adalah ketika Anda membangun pohon ekspresi secara manual untuk tujuan mengkompilasi ke lambda, seperti yang dilakukan oleh kode operator generik .
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 kebutuhan untuk ekspresi maka Anda mungkin tidak perlu menggunakannya.
Alasan utamanya adalah ketika Anda tidak ingin menjalankan kode secara langsung, tetapi ingin memeriksanya. Ini bisa karena sejumlah alasan:
Expression
bisa saja mustahil untuk diserialisasi sebagai delegasi, karena ekspresi apa pun dapat berisi permohonan dari delegasi / metode referensi sewenang-wenang. "Mudah" itu relatif, tentu saja.
Saya belum melihat jawaban apa pun yang menyebutkan kinerja. Melewati Func<>
s ke Where()
atau Count()
buruk. Sangat buruk. Jika Anda menggunakan a Func<>
maka itu memanggil hal- IEnumerable
hal LINQ bukan IQueryable
, yang berarti bahwa seluruh tabel ditarik dan kemudian disaring. Expression<Func<>>
secara signifikan lebih cepat, terutama jika Anda meminta database yang tinggal di server lain.