Saya sedang mengerjakan fasilitas penyelesaian (intellisense) untuk C # di emacs.
Idenya adalah, jika pengguna mengetikkan sebuah fragmen, kemudian meminta penyelesaian melalui kombinasi penekanan tombol tertentu, fasilitas penyelesaian akan menggunakan refleksi .NET untuk menentukan penyelesaian yang mungkin.
Melakukan ini mengharuskan jenis hal yang diselesaikan, diketahui. Jika itu sebuah string, ada satu set metode dan properti yang diketahui; jika itu Int32, itu memiliki set terpisah, dan seterusnya.
Dengan menggunakan semantik, paket kode lexer / parser yang tersedia di emacs, saya dapat menemukan deklarasi variabel, dan tipenya. Mengingat itu, sangatlah mudah untuk menggunakan refleksi untuk mendapatkan metode dan properti pada tipe, dan kemudian menyajikan daftar opsi kepada pengguna. (Ok, tidak cukup mudah untuk dilakukan dalam emacs, tetapi menggunakan kemampuan untuk menjalankan proses PowerShell di dalam emacs , itu menjadi jauh lebih mudah. Saya menulis rakitan .NET khusus untuk melakukan refleksi, memuatnya ke PowerShell, dan kemudian menjalankan elisp di dalamnya emacs dapat mengirim perintah ke PowerShell dan membaca tanggapan, melalui comint. Hasilnya emacs bisa mendapatkan hasil refleksi dengan cepat.)
Masalahnya muncul ketika kode digunakan var
dalam deklarasi hal yang sedang diselesaikan. Artinya, jenisnya tidak ditentukan secara eksplisit, dan penyelesaian tidak akan berfungsi.
Bagaimana saya bisa dengan andal menentukan tipe sebenarnya yang digunakan, ketika variabel dideklarasikan dengan var
kata kunci? Untuk memperjelas, saya tidak perlu menentukannya saat runtime. Saya ingin menentukannya pada "Waktu desain".
Sejauh ini saya punya ide ini:
- kompilasi dan panggil:
- ekstrak pernyataan deklarasi, misalnya `var foo =" a string value ";`
- menggabungkan pernyataan `foo.GetType ();`
- secara dinamis mengkompilasi fragmen C # yang dihasilkan ke dalam assembly baru
- muat rakitan ke AppDomain baru, jalankan framgment dan dapatkan jenis pengembalian.
- bongkar dan buang rakitan
Saya tahu bagaimana melakukan semua ini. Tapi kedengarannya sangat berat, untuk setiap permintaan penyelesaian di editor.
Saya kira saya tidak membutuhkan AppDomain baru setiap saat. Saya dapat menggunakan kembali AppDomain tunggal untuk beberapa rakitan sementara, dan mengamortisasi biaya pengaturan dan menghancurkannya, di beberapa permintaan penyelesaian. Itu lebih merupakan perubahan dari ide dasarnya.
- mengkompilasi dan memeriksa IL
Cukup kompilasi deklarasi menjadi modul, lalu periksa IL, untuk menentukan tipe sebenarnya yang disimpulkan oleh kompilator. Bagaimana ini mungkin? Apa yang akan saya gunakan untuk memeriksa IL?
Ada ide yang lebih baik di luar sana? Komentar? saran?
EDIT - memikirkan hal ini lebih lanjut, kompilasi-dan-pemanggilan tidak dapat diterima, karena pemanggilan mungkin memiliki efek samping. Jadi, opsi pertama harus dikesampingkan.
Juga, saya rasa saya tidak bisa mengasumsikan adanya .NET 4.0.
PEMBARUAN - Jawaban yang benar, tidak disebutkan di atas, tetapi dengan lembut ditunjukkan oleh Eric Lippert, adalah dengan menerapkan sistem inferensi tipe fidelitas penuh. Ini adalah satu-satunya cara untuk menentukan jenis var pada waktu desain. Tapi, itu juga tidak mudah dilakukan. Karena saya tidak mengalami ilusi bahwa saya ingin mencoba membangun hal seperti itu, saya mengambil jalan pintas dari opsi 2 - mengekstrak kode deklarasi yang relevan, dan mengkompilasinya, kemudian memeriksa IL yang dihasilkan.
Ini benar-benar berfungsi, untuk subset yang adil dari skenario penyelesaian.
Misalnya, dalam fragmen kode berikut,? adalah posisi di mana pengguna meminta penyelesaian. Ini bekerja:
var x = "hello there";
x.?
Penyelesaian menyadari bahwa x adalah String, dan menyediakan opsi yang sesuai. Ini dilakukan dengan membuat dan kemudian menyusun kode sumber berikut:
namespace N1 {
static class dmriiann5he { // randomly-generated class name
static void M1 () {
var x = "hello there";
}
}
}
... dan kemudian memeriksa IL dengan refleksi sederhana.
Ini juga berfungsi:
var x = new XmlDocument();
x.?
Mesin menambahkan klausa penggunaan yang sesuai ke kode sumber yang dihasilkan, sehingga dapat dikompilasi dengan benar, lalu inspeksi IL-nya sama.
Ini juga berfungsi:
var x = "hello";
var y = x.ToCharArray();
var z = y.?
Ini hanya berarti inspeksi IL harus menemukan jenis variabel lokal ketiga, bukan yang pertama.
Dan ini:
var foo = "Tra la la";
var fred = new System.Collections.Generic.List<String>
{
foo,
foo.Length.ToString()
};
var z = fred.Count;
var x = z.?
... yang hanya satu tingkat lebih dalam dari contoh sebelumnya.
Tapi, yang tidak berhasil adalah penyelesaian pada variabel lokal yang inisialisasinya bergantung pada titik mana pun pada anggota instance, atau argumen metode lokal. Suka:
var foo = this.InstanceMethod();
foo.?
Juga sintaks LINQ.
Saya harus memikirkan betapa berharganya hal-hal itu sebelum saya mempertimbangkan untuk mengatasinya melalui apa yang pasti merupakan "desain terbatas" (kata sopan untuk retasan) untuk penyelesaian.
Pendekatan untuk mengatasi masalah dengan dependensi pada argumen metode atau metode instance adalah dengan mengganti, dalam fragmen kode yang dihasilkan, dikompilasi, dan kemudian dianalisis IL, referensi ke hal-hal tersebut dengan variabel lokal "sintetik" dari jenis yang sama.
Pembaruan Lain - penyelesaian pada vars yang bergantung pada anggota instance, sekarang berfungsi.
Apa yang saya lakukan adalah menginterogasi jenisnya (melalui semantik), dan kemudian menghasilkan anggota siaga sintetis untuk semua anggota yang ada. Untuk buffer C # seperti ini:
public class CsharpCompletion
{
private static int PrivateStaticField1 = 17;
string InstanceMethod1(int index)
{
...lots of code here...
return result;
}
public void Run(int count)
{
var foo = "this is a string";
var fred = new System.Collections.Generic.List<String>
{
foo,
foo.Length.ToString()
};
var z = fred.Count;
var mmm = count + z + CsharpCompletion.PrivateStaticField1;
var nnn = this.InstanceMethod1(mmm);
var fff = nnn.?
...more code here...
... kode yang dihasilkan yang dikompilasi, sehingga saya dapat belajar dari output IL jenis var nnn lokal, terlihat seperti ini:
namespace Nsbwhi0rdami {
class CsharpCompletion {
private static int PrivateStaticField1 = default(int);
string InstanceMethod1(int index) { return default(string); }
void M0zpstti30f4 (int count) {
var foo = "this is a string";
var fred = new System.Collections.Generic.List<String> { foo, foo.Length.ToString() };
var z = fred.Count;
var mmm = count + z + CsharpCompletion.PrivateStaticField1;
var nnn = this.InstanceMethod1(mmm);
}
}
}
Semua anggota instance dan tipe statis tersedia dalam kode kerangka. Ini berhasil dikompilasi. Pada titik itu, menentukan jenis var lokal secara langsung melalui Refleksi.
Yang memungkinkan hal ini adalah:
- kemampuan untuk menjalankan PowerShell di emacs
- kompiler C # sangat cepat. Di komputer saya, dibutuhkan sekitar 0,5 detik untuk mengkompilasi rakitan dalam memori. Tidak cukup cepat untuk analisis antar-penekanan tombol, tetapi cukup cepat untuk mendukung pembuatan daftar penyelesaian sesuai permintaan.
Saya belum melihat ke LINQ.
Itu akan menjadi masalah yang jauh lebih besar karena lexer / parser semantik emacs memiliki C #, tidak "melakukan" LINQ.