我正在为 Emacs 中的 C # 开发一个完成(智能感知)工具。
这个想法是,如果用户键入一个片段,然后要求通过一个特定的击键组合完成,完成工具将使用。NET 反射来确定可能的完成。
这样做需要知道要完成的事情的类型。如果它是一个字符串,则有一组已知的可能的方法和属性; 如果它是一个 Int32,则有一组独立的方法和属性,依此类推。
通过使用 emacs 中提供的代码 lexer/parser 包——语义,我可以定位变量声明及其类型。因此,可以直接使用反射来获取类型的方法和属性,然后向用户显示选项列表。(好吧,不完全 直截了当做 内心 emacs,但使用 在 emacs 中运行 Powershell 进程的能力,它变得容易得多。我写了一个习俗。NET 程序集进行反射,将其加载到 powershell 中,然后在 emacs 中运行的 elisp 可以通过 comint 向 powershell 发送命令并读取响应。因此,emacs 可以很快得到反射的结果。)
当代码在完成事情的声明中使用 var
时,问题就出现了。这意味着没有显式指定类型,并且补全不起作用。
当使用 var
关键字声明变量时,如何可靠地确定所使用的实际类型?澄清一下,我不需要在运行时确定它。我想在“设计时间”决定。
到目前为止,我有这些想法:
我知道如何做到这一切。但是,对于编辑器中的每个完成请求来说,这听起来非常重要。
我想我不需要每次都换一个新的应用程序域名。我可以为多个临时程序集重用单个 AppDomain,并分期偿还设置它的成本 在多个完成请求之间上升和拆除它。这更多的是一个基本思想的调整。
只需将声明编译成一个模块,然后检查 IL,以确定编译器推断的实际类型。这怎么可能?我将使用什么来检查 IL?
有更好的主意吗? 评论? 建议?
EDIT -进一步考虑这个问题,编译和调用是不可接受的,因为调用可能有副作用。所以必须排除第一个选择。
此外,我认为我不能假设存在.NET 4.0。
UPDATE -正确的答案,上面没有提到,但是 Eric Lippert 温和地指出,是实现一个完全保真的类型推理系统。这是在设计时可靠地确定变量类型的唯一方法。但是,这也不容易做到。因为我没有幻想过要尝试构建这样一个东西,所以我选择了选项2的捷径——提取相关的声明代码,编译它,然后检查产生的 IL。
对于完成场景的一个公平子集,这实际上是有效的。
例如,假设在下面的代码片段中,?是用户要求完成的位置。这种方法是有效的:
var x = "hello there";
x.?
补全实现了 x 是一个 String,并提供了适当的选项。它通过生成并编译以下源代码来完成这项工作:
namespace N1 {
static class dmriiann5he { // randomly-generated class name
static void M1 () {
var x = "hello there";
}
}
}
然后用简单的反射检查 IL。
这也行得通:
var x = new XmlDocument();
x.?
引擎将适当的 using 子句添加到生成的源代码中,以便正确编译,然后 IL 检查是相同的。
这也行得通:
var x = "hello";
var y = x.ToCharArray();
var z = y.?
它只是意味着 IL 检查必须找到第三个局部变量的类型,而不是第一个。
还有这个:
var foo = "Tra la la";
var fred = new System.Collections.Generic.List<String>
{
foo,
foo.Length.ToString()
};
var z = fred.Count;
var x = z.?
只比前面的例子深一层。
但是,没有的工作原理是完成任何局部变量,这些局部变量的初始化在任何时候都取决于实例成员或局部方法参数。比如:
var foo = this.InstanceMethod();
foo.?
也没有 LINQ 语法。
在我考虑通过明确的“有限设计”(用礼貌的话来说就是“黑客”)来完成它们之前,我必须先考虑这些东西的价值。
解决依赖于方法参数或实例方法的问题的一种方法是,在生成、编译然后进行 IL 分析的代码片段中,用同一类型的“合成”局部变量替换对这些东西的引用。
另一个更新 ——对依赖于实例成员的 vars 的完成,现在可以工作了。
我所做的是询问类型(通过语义) ,然后为所有现有成员生成合成替身成员。对于这样的 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 中学习本地 var 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);
}
}
}
所有实例和静态类型成员都可以在框架代码中找到。它成功地编译了。此时,通过反射确定局部变量的类型就很简单了。
让这一切成为可能的是:
我还没有调查 LINQ。
这将是一个更大的问题,因为语义 lexer/解析器 emacs 为 C # 提供的,不“做”LINQ。