私はemacsでC#の補完(インテリセンス)機能に取り組んでいます。
ユーザーがフラグメントを入力し、特定のキーストロークの組み合わせを介して補完を要求すると、補完機能は.NETリフレクションを使用して可能な補完を決定するという考え方です。
これを行うには、完成するものの種類を知っている必要があります。文字列の場合、考えられるメソッドとプロパティの既知のセットがあります。それがInt32の場合は、別個のセットがあります。
emacsで利用可能なコードレクサー/パーサーパッケージのセマンティックを使用して、変数宣言とその型を見つけることができます。そのため、リフレクションを使用して型のメソッドとプロパティを取得し、オプションのリストをユーザーに提示するのは簡単です。(OK、emacs 内で実行するのは簡単ではありませんが、 emacs 内でpowershellプロセスを実行する機能を使用すると、はるかに簡単になります。リフレクションを実行するためのカスタム.NETアセンブリを記述し、それをpowershellにロードしてから、elisp内で実行しますemacsはコマンドをpowershellに送信し、comintを介して応答を読み取ることができます。その結果、emacsはリフレクションの結果をすばやく取得できます。)
問題var
は、完了したものの宣言でコードが使用するときに発生します。つまり、タイプが明示的に指定されておらず、補完が機能しません。
変数がvar
キーワードで宣言されている場合、実際に使用されているタイプを確実に判断するにはどうすればよいですか?明確にするために、実行時にそれを決定する必要はありません。「設計時」に決定したい。
これまでのところ、私はこれらのアイデアを持っています:
- コンパイルして呼び出す:
- 宣言ステートメントを抽出します。例: `var foo =" a string value ";`
- ステートメント `foo.GetType();`を連結します
- 結果のC#フラグメントを動的にコンパイルして新しいアセンブリに
- アセンブリを新しいAppDomainに読み込み、フレームを実行して戻り値の型を取得します。
- アセンブリをアンロードして破棄する
私はこれをすべて行う方法を知っています。しかし、エディターでの完了要求ごとに、それはひどく重いように聞こえます。
私は毎回新しいAppDomainを必要としないと思います。単一のAppDomainを複数の一時的なアセンブリに再利用し、複数の完了リクエストにまたがって、それを設定および破棄するコストを償却できます。これは、基本的な考え方の微調整です。
- ILのコンパイルと検査
宣言をモジュールにコンパイルし、ILを検査して、コンパイラーによって推論された実際のタイプを判別します。これはどのようにして可能でしょうか?ILの検査には何を使用しますか?
そこにもっと良いアイデアはありますか?コメント?提案?
編集 -これについてさらに考えると、コンパイルと呼び出しは受け入れられません。呼び出しには副作用がある可能性があるためです。したがって、最初のオプションは除外する必要があります。
また、.NET 4.0の存在は想定できません。
更新 -正解は、上記には言及されていませんが、エリックリッパートによって穏やかに指摘されていますが、完全な忠実度の型推論システムを実装することです。これは、設計時に変数のタイプを確実に決定する唯一の方法です。しかし、それも簡単ではありません。私はそのようなものを構築したいという幻想に苦しんでいないので、オプション2のショートカットをとりました-関連する宣言コードを抽出してコンパイルし、結果のILを検査します。
これは、完了シナリオのかなりのサブセットに対して実際に機能します。
たとえば、次のコードフラグメントで、?ユーザーが完了を要求する位置です。これは機能します:
var x = "hello there";
x.?
補完により、xは文字列であることがわかり、適切なオプションが提供されます。これは、次のソースコードを生成してコンパイルすることによって行われます。
namespace N1 {
static class dmriiann5he { // randomly-generated class name
static void M1 () {
var x = "hello there";
}
}
}
...そして単純な反射でILを検査します。
これも機能します:
var x = new XmlDocument();
x.?
エンジンは適切に使用する句を生成されたソースコードに追加するため、適切にコンパイルされ、IL検査は同じになります。
これも機能します:
var x = "hello";
var y = x.ToCharArray();
var z = y.?
これは、IL検査が最初のローカル変数ではなく、3番目のローカル変数のタイプを検出する必要があることを意味します。
この:
var foo = "Tra la la";
var fred = new System.Collections.Generic.List<String>
{
foo,
foo.Length.ToString()
};
var z = fred.Count;
var x = z.?
...これは前の例よりも1レベルだけ深いです。
しかし、機能しないのは、初期化がインスタンスメンバーの任意の時点で依存するローカル変数、またはローカルメソッド引数の完了です。お気に入り:
var foo = this.InstanceMethod();
foo.?
LINQ構文もありません。
完成させるために間違いなく「限定デザイン」(ハックの礼儀正しい言葉)であるものを使ってそれらに対処することを検討する前に、それらがどれほど価値があるかについて考えなければなりません。
メソッド引数またはインスタンスメソッドへの依存性の問題に対処するアプローチは、生成され、コンパイルされ、IL分析されるコードのフラグメントで、それらへの参照を同じタイプの「合成」ローカル変数で置き換えることです。
別の更新 -インスタンスメンバーに依存する変数の補完が機能するようになりました。
私が行ったのは、(セマンティックを介して)タイプを問い合わせてから、既存のすべてのメンバーの合成代用メンバーを生成することでした。このようなC#バッファーの場合:
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...
...コンパイルされて生成されたコードで、出力ILからローカル変数nnnのタイプを知ることができるように、次のようになります。
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);
}
}
}
すべてのインスタンスおよび静的型メンバーは、スケルトンコードで使用できます。正常にコンパイルされます。その時点で、ローカル変数のタイプを決定することは、リフレクションを介して簡単です。
これを可能にするのは:
- emacsでPowerShellを実行する機能
- C#コンパイラは本当に高速です。私のマシンでは、インメモリアセンブリをコンパイルするのに約0.5秒かかります。キーストローク間分析には十分高速ではありませんが、完了リストのオンデマンド生成をサポートするには十分高速です。
私はまだLINQを調べていません。
C#のセマンティックレクサー/パーサーemacsはLINQを「実行」しないため、これははるかに大きな問題になります。