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>
?
c#
delegates
lambda
expression-trees
Richard Nagle
sumber
sumber
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 dariFunc<T>
.Func<T>
menunjukkan suatudelegate
yang cukup banyak penunjuk ke metode danExpression<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 (denganExpression.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.akan secara efektif mengkompilasi ke metode IL yang tidak mendapatkan apa pun dan mengembalikan 10.
akan dikonversi ke struktur data yang menggambarkan ekspresi yang tidak memiliki parameter dan mengembalikan nilai 10:
gambar yang lebih besar
Walaupun keduanya terlihat sama pada waktu kompilasi, apa yang dihasilkan oleh kompiler sama sekali berbeda .
sumber
Expression
berisi informasi meta tentang delegasi tertentu.Expression<Func<...>>
bukan hanyaFunc<...>
.(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:
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 dalamExpression<Func<T, bool>>
, jadi saya googled mengapa membutuhkanExpression
bukanFunc
, berakhir di sini.Ekspresi hanya mengubah delegasi menjadi data tentang dirinya sendiri. Jadi
a => a + 1
menjadi sesuatu seperti "Di sisi kiri adaint 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
, danFunc
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 menerimaExpression
. Ketika Anda lulus suatuExpression
untuk itu, Anda menyimpan IQueryable sebagai hasilnya, tetapi ketika Anda lulusFunc
, 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.sumber
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)
sumber
Saya ingin menambahkan beberapa catatan tentang perbedaan antara
Func<T>
danExpression<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.
sumber
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:
sumber
database.data.Where(i => i.Id > 0)
dieksekusi sebagaiSELECT FROM [data] WHERE [id] > 0
. Jika Anda hanya lulus dalam Func, Anda telah menempatkan penutup mata pada driver Anda dan semua itu dapat dilakukan adalahSELECT *
dan kemudian setelah itu dimuat semua data ke dalam memori, iterate melalui masing-masing dan menyaring segala sesuatu dengan id> 0. Wrapping AndaFunc
diExpression
memberdayakan driver untuk menganalisisFunc
dan mengubahnya menjadi permintaan Sql / MongoDb / lainnya.Expression
tetapi ketika saya sedang berlibur akanFunc/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:
Ini mendekonstruksi pohon ekspresi untuk menyelesaikan
SomeMethod
(dan nilai setiap argumen), melakukan panggilan RPC, memperbarui setiapref
/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 .
sumber
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.
sumber
Alasan utamanya adalah ketika Anda tidak ingin menjalankan kode secara langsung, tetapi ingin memeriksanya. Ini bisa karena sejumlah alasan:
sumber
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 keWhere()
atauCount()
buruk. Sangat buruk. Jika Anda menggunakan aFunc<>
maka itu memanggil hal-IEnumerable
hal LINQ bukanIQueryable
, 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.sumber