Pohon ekspresi - data yang menentukan kode

Pohon Ekspresi adalah struktur data yang menentukan kode. Pohon ekspresi didasarkan pada struktur yang sama dengan yang digunakan pengkompilasi untuk menganalisis kode dan menghasilkan output yang dikompilasi. Saat Anda membaca artikel ini, Anda melihat sedikit kesamaan antara Pohon Ekspresi dan jenis yang digunakan dalam API Roslyn untuk membangun Analyzer dan CodeFixes. (Penganalisis dan CodeFix adalah paket NuGet yang melakukan analisis statis pada kode dan menyarankan perbaikan potensial untuk pengembang.) Konsepnya serupa, dan hasil akhirnya adalah struktur data yang memungkinkan pemeriksaan kode sumber dengan cara yang bermakna. Namun, Pohon Ekspresi didasarkan pada serangkaian kelas dan API yang berbeda dari API Roslyn. Berikut adalah baris kode:

var sum = 1 + 2;

Jika Anda menganalisis kode sebelumnya sebagai pohon ekspresi, pohon berisi beberapa simpul. Simpul terluar adalah pernyataan deklarasi variabel dengan penugasan (var sum = 1 + 2;) Simpul terluar tersebut berisi beberapa simpul anak: deklarasi variabel, operator penugasan, dan ekspresi yang mewakili sisi kanan tanda sama dengan. Ekspresi tersebut dibagi lebih lanjut menjadi ekspresi yang mewakili operasi penambahan, dan operan kiri dan kanan penambahan.

Mari kita telusuri sedikit lebih dalam ekspresi yang membentuk sisi kanan tanda sama. Ekspresinya adalah 1 + 2, ekspresi biner. Lebih khusus lagi, ini adalah ekspresi penambahan biner. Ekspresi penambahan biner memiliki dua cabang, yang masing-masing merupakan simpul kiri dan kanan dari ekspresi penambahan. Di sini, kedua simpul adalah ekspresi konstan: Operand kiri adalah nilai 1, dan operand kanan adalah nilai 2.

Secara visual, seluruh pernyataan adalah pohon: Anda dapat memulai di simpul akar, dan melakukan perjalanan ke setiap simpul di pohon untuk melihat kode yang membentuk pernyataan:

  • Pernyataan deklarasi variabel dengan penugasan (var sum = 1 + 2;)
    • Deklarasi jenis variabel implisit (var sum)
      • Kata kunci var implisit (var)
      • Deklarasi nama variabel (sum)
    • Operator penugasan (=)
    • Ekspresi penambahan biner (1 + 2)
      • Operan kiri (1)
      • Operator penambahan (+)
      • Operan kanan (2)

Pohon sebelumnya mungkin terlihat rumit, tetapi sangat kuat. Setelah proses yang sama, Anda menguraikan ekspresi yang jauh lebih rumit. Pertimbangkan ekspresi ini:

var finalAnswer = this.SecretSauceFunction(
    currentState.createInterimResult(), currentState.createSecondValue(1, 2),
    decisionServer.considerFinalOptions("hello")) +
    MoreSecretSauce('A', DateTime.Now, true);

Ekspresi sebelumnya juga merupakan deklarasi variabel dengan penugasan. Dalam konteks ini, sisi kanan dari penugasan adalah pohon yang jauh lebih rumit. Anda tidak akan menguraikan ekspresi ini, tetapi pertimbangkan apa node-node yang berbeda mungkin adalah. Ada panggilan metode yang menggunakan objek saat ini sebagai penerima: satu yang memiliki penerima eksplisit this dan satu lagi yang tidak. Ada pemanggilan fungsi menggunakan objek penerima lain, ada argumen konstan dari berbagai jenis. Dan akhirnya, ada operator penjumlahan biner. Tergantung pada jenis pengembalian dari SecretSauceFunction() atau MoreSecretSauce(), operator penambahan biner tersebut mungkin merupakan panggilan metode ke operator penambahan yang telah ditimpa, yang kemudian dilakukan sebagai panggilan metode statis ke operator penambahan biner yang ditentukan untuk kelas.

Terlepas dari kompleksitas yang dirasakan ini, ekspresi sebelumnya membuat struktur pohon yang dinavigasi semudah sampel pertama. Anda terus menelusuri simpul anak untuk menemukan simpul daun dalam sebuah ekspresi. Simpul induk memiliki referensi ke anak-anak mereka, dan setiap simpul memiliki properti yang menjelaskan jenis node itu.

Struktur pohon ekspresi sangat konsisten. Setelah mempelajari dasar-dasarnya, Anda memahami bahkan kode yang paling kompleks ketika direpresentasikan sebagai pohon ekspresi. Keanggunan dalam struktur data menjelaskan bagaimana pengkompilasi C# menganalisis program C# yang paling kompleks dan membuat output yang tepat dari kode sumber yang rumit tersebut.

Setelah Anda terbiasa dengan struktur pohon ekspresi, Anda menemukan bahwa pengetahuan yang Anda peroleh dengan cepat memungkinkan Anda untuk bekerja dengan banyak skenario yang lebih canggih. Ekspresi pohon memiliki kekuatan luar biasa.

Selain menerjemahkan algoritma untuk dijalankan di lingkungan lain, pohon ekspresi memudahkan untuk menulis algoritma yang memeriksa kode sebelum mengeksekusinya. Anda menulis metode yang argumennya adalah ekspresi dan kemudian memeriksa ekspresi tersebut sebelum mengeksekusi kode. Pohon Ekspresi adalah representasi lengkap kode: Anda melihat nilai subekspresi apa pun. Anda melihat nama metode dan properti. Anda melihat nilai ekspresi konstanta apa pun. Anda mengonversi pohon ekspresi menjadi delegasi yang dapat dieksekusi, dan menjalankan kode.

API untuk Pohon Ekspresi memungkinkan Anda membuat pohon yang mewakili hampir semua konstruksi kode yang valid. Namun, untuk menjaga hal-hal sesingkat mungkin, beberapa idiom C# tidak dapat dibuat di pohon ekspresi. Salah satu contohnya adalah ekspresi asinkron (menggunakan async kata kunci dan await ). Jika kebutuhan Anda memerlukan algoritma asinkron, Anda harus memanipulasi Task objek secara langsung, daripada mengandalkan dukungan kompilator. Salah satu contohnya adalah membuat perulangan. Biasanya, Anda membuat perulangan ini dengan menggunakan for, , foreachwhile atau do perulangan. Seperti yang Anda lihat nanti dalam seri ini, APIs pohon ekspresi mendukung ekspresi perulangan tunggal, dengan ekspresi break dan continue yang mengontrol pengulangan.

Satu hal yang tidak dapat Anda lakukan adalah memodifikasi pohon ekspresi. Pohon Ekspresi adalah struktur data yang tidak dapat diubah. Jika Anda ingin memutasi (mengubah) pohon ekspresi, Anda harus membuat pohon baru yang merupakan salinan aslinya, tetapi dengan perubahan yang Anda inginkan.