Jawaban singkat:
Operator kutipan adalah operator yang menginduksi semantik penutupan pada operannya . Konstanta hanyalah nilai.
Kutipan dan konstanta memiliki arti yang berbeda dan oleh karena itu memiliki representasi berbeda dalam pohon ekspresi . Memiliki representasi yang sama untuk dua hal yang sangat berbeda sangat membingungkan dan rentan bug.
Jawaban panjang:
Pertimbangkan hal berikut:
(int s)=>(int t)=>s+t
Lambda luar adalah pabrik untuk penambah yang terikat ke parameter lambda luar.
Sekarang, misalkan kita ingin menampilkan ini sebagai pohon ekspresi yang nantinya akan dikompilasi dan dijalankan. Seharusnya tubuh pohon ekspresi itu seperti apa? Itu tergantung pada apakah Anda ingin status yang dikompilasi mengembalikan delegasi atau pohon ekspresi.
Mari kita mulai dengan mengabaikan kasus yang tidak menarik. Jika kami ingin mengembalikan delegasi, maka pertanyaan apakah akan menggunakan Kutipan atau Konstan adalah poin yang diperdebatkan:
var ps = Expression.Parameter(typeof(int), "s");
var pt = Expression.Parameter(typeof(int), "t");
var ex1 = Expression.Lambda(
Expression.Lambda(
Expression.Add(ps, pt),
pt),
ps);
var f1a = (Func<int, Func<int, int>>) ex1.Compile();
var f1b = f1a(100);
Console.WriteLine(f1b(123));
Lambda memiliki lambda bersarang; kompilator menghasilkan lambda interior sebagai delegasi ke fungsi yang ditutup di atas status fungsi yang dihasilkan untuk lambda luar. Kasus ini tidak perlu kita pertimbangkan lagi.
Misalkan kita ingin keadaan yang dikompilasi mengembalikan pohon ekspresi interior. Ada dua cara untuk melakukannya: cara mudah dan cara sulit.
Cara yang sulit adalah mengatakannya, bukan
(int s)=>(int t)=>s+t
yang kami maksud adalah
(int s)=>Expression.Lambda(Expression.Add(...
Dan kemudian buat pohon ekspresi untuk itu , menghasilkan kekacauan ini :
Expression.Lambda(
Expression.Call(typeof(Expression).GetMethod("Lambda", ...
bla bla bla, lusinan baris kode refleksi untuk membuat lambda. Tujuan dari operator kutipan adalah untuk memberi tahu penyusun pohon ekspresi bahwa kita ingin lambda yang diberikan diperlakukan sebagai pohon ekspresi, bukan sebagai fungsi, tanpa harus secara eksplisit menghasilkan kode pembuatan pohon ekspresi .
Cara mudahnya adalah:
var ex2 = Expression.Lambda(
Expression.Quote(
Expression.Lambda(
Expression.Add(ps, pt),
pt)),
ps);
var f2a = (Func<int, Expression<Func<int, int>>>)ex2.Compile();
var f2b = f2a(200).Compile();
Console.WriteLine(f2b(123));
Dan memang, jika Anda mengkompilasi dan menjalankan kode ini, Anda mendapatkan jawaban yang benar.
Perhatikan bahwa operator kutipan adalah operator yang menginduksi semantik penutupan pada lambda interior yang menggunakan variabel luar, parameter formal dari lambda luar.
Pertanyaannya adalah: mengapa tidak menghilangkan Kutipan dan membuat ini melakukan hal yang sama?
var ex3 = Expression.Lambda(
Expression.Constant(
Expression.Lambda(
Expression.Add(ps, pt),
pt)),
ps);
var f3a = (Func<int, Expression<Func<int, int>>>)ex3.Compile();
var f3b = f3a(300).Compile();
Console.WriteLine(f3b(123));
Konstanta tidak menyebabkan semantik penutupan. Kenapa harus begitu? Anda mengatakan bahwa ini adalah sebuah konstanta . Itu hanya sebuah nilai. Ini harus sempurna saat diserahkan ke kompiler; compiler harus dapat menghasilkan dump nilai tersebut ke stack yang dibutuhkan.
Karena tidak ada penutupan yang diinduksi, jika Anda melakukan ini, Anda akan mendapatkan pengecualian "variabel 'tipe' System.Int32 'tidak ditentukan" pada pemanggilan.
(Selain: Saya baru saja meninjau generator kode untuk pembuatan delegasi dari pohon ekspresi yang dikutip, dan sayangnya komentar yang saya masukkan ke dalam kode pada tahun 2006 masih ada. FYI, parameter luar yang diangkat snapshotted menjadi sebuah konstanta ketika dikutip pohon ekspresi direifikasi sebagai delegasi oleh runtime compiler.Ada alasan bagus mengapa saya menulis kode seperti itu yang tidak saya ingat pada saat ini, tetapi memiliki efek samping yang buruk yaitu memperkenalkan penutupan atas nilai parameter luar daripada menutup variabel. Rupanya tim yang mewarisi kode itu memutuskan untuk tidak memperbaiki cacat itu, jadi jika Anda mengandalkan mutasi parameter luar tertutup yang diamati dalam lambda interior yang dikutip terkompilasi, Anda akan kecewa. Namun, karena ini adalah praktik pemrograman yang sangat buruk untuk (1) mengubah parameter formal dan (2) bergantung pada mutasi variabel luar, saya sarankan Anda mengubah program Anda untuk tidak menggunakan dua praktik pemrograman yang buruk ini, daripada menunggu perbaikan yang tampaknya tidak akan datang. Maaf atas kesalahannya.)
Jadi, ulangi pertanyaannya:
Kompiler C # bisa saja dibuat untuk mengkompilasi ekspresi lambda bersarang ke dalam pohon ekspresi yang melibatkan Expression.Constant () daripada Expression.Quote (), dan penyedia kueri LINQ apa pun yang ingin memproses pohon ekspresi ke dalam beberapa bahasa kueri lain (seperti SQL ) bisa mencari ConstantExpression dengan tipe Expression daripada UnaryExpression dengan tipe node Kutipan khusus, dan yang lainnya akan sama.
Anda benar. Kita bisa menyandikan informasi semantik yang berarti "menyebabkan penutupan semantik pada nilai ini" dengan menggunakan tipe ekspresi konstan sebagai sebuah tanda .
"Konstanta" kemudian akan memiliki arti "menggunakan nilai konstanta ini, kecuali jika jenisnya adalah jenis pohon ekspresi dan nilainya adalah pohon ekspresi yang valid, dalam hal ini, gunakan nilai yang merupakan pohon ekspresi yang dihasilkan dari penulisan ulang interior pohon ekspresi yang diberikan untuk menginduksi semantik penutupan dalam konteks lambda luar mana pun yang mungkin kita berada sekarang.
Tapi mengapa akan kita melakukan hal yang gila? Operator kutipan adalah operator yang sangat rumit , dan harus digunakan secara eksplisit jika Anda akan menggunakannya. Anda menyarankan agar agar lebih hemat tentang tidak menambahkan satu metode pabrik tambahan dan jenis node di antara beberapa lusin yang sudah ada di sana, kami menambahkan kasus sudut yang aneh ke konstanta, sehingga konstanta terkadang secara logis konstan, dan terkadang ditulis ulang lambda dengan semantik penutupan.
Ini juga akan memiliki efek yang agak aneh bahwa konstanta tidak berarti "gunakan nilai ini". Misalkan untuk beberapa alasan aneh Anda ingin kasus ketiga di atas mengkompilasi pohon ekspresi menjadi delegasi yang membagikan pohon ekspresi yang memiliki referensi yang tidak ditulis ulang ke variabel luar? Mengapa? Mungkin karena Anda menguji kompiler dan ingin meneruskan konstanta sehingga Anda dapat melakukan analisis lain nanti. Proposal Anda akan membuat itu tidak mungkin; setiap konstanta yang kebetulan merupakan tipe pohon ekspresi akan ditulis ulang. Seseorang memiliki harapan yang masuk akal bahwa "konstan" berarti "menggunakan nilai ini". "Constant" adalah node "lakukan apa yang saya katakan". Prosesor konstan ' untuk mengatakan berdasarkan jenisnya.
Dan tentu saja perhatikan bahwa Anda sekarang meletakkan beban pemahaman (yaitu, memahami bahwa konstanta memiliki semantik rumit yang berarti "konstan" dalam satu kasus dan "menyebabkan semantik penutupan" berdasarkan bendera yang ada dalam sistem tipe ) pada setiap penyedia yang melakukan analisis semantik pohon ekspresi, tidak hanya pada penyedia Microsoft. Berapa banyak dari penyedia pihak ketiga itu yang salah?
"Kutipan" melambai-lambaikan bendera merah besar yang bertuliskan "hai sobat, lihat ke sini, saya adalah ekspresi lambda bersarang dan saya memiliki semantik aneh jika saya menutup variabel luar!" sedangkan "Konstan" mengatakan "Saya tidak lebih dari nilai; gunakan saya sesuai keinginan Anda." Ketika ada sesuatu yang rumit dan berbahaya, kami ingin membuatnya mengibarkan bendera merah, tidak menyembunyikan fakta itu dengan membuat pengguna menggali melalui sistem tipe untuk mengetahui apakah nilai ini istimewa atau tidak.
Selain itu, gagasan bahwa menghindari redundansi bahkan merupakan tujuan adalah salah. Tentu, menghindari redundansi yang tidak perlu dan membingungkan adalah sebuah tujuan, tetapi kebanyakan redundansi adalah hal yang baik; redundansi menciptakan kejelasan. Metode pabrik baru dan jenis node murah . Kami dapat membuat sebanyak yang kami butuhkan sehingga masing-masing mewakili satu operasi dengan rapi. Kita tidak perlu menggunakan trik jahat seperti "ini berarti satu hal kecuali bidang ini disetel ke benda ini, dalam hal ini berarti sesuatu yang lain."