我正在为emacs中的C#完成(智能)功能。
这个想法是,如果用户键入一个片段,然后通过特定的按键组合要求完成,则完成设施将使用.NET反射来确定可能的完成。
这样做需要知道完成的事物的类型。如果是字符串,则存在一组已知的可能方法和属性。如果是Int32,则具有单独的集合,依此类推。
使用语义,emacs中提供了一个代码lexer / parser程序包,我可以找到变量声明及其类型。鉴于此,很容易使用反射来获取类型上的方法和属性,然后向用户显示选项列表。(好吧,在 emacs中执行操作不是很简单,但是使用在 emacs 中运行powershell进程的功能会变得容易得多。我编写了一个自定义.NET程序集进行反射,将其加载到powershell中,然后在运行时在elisp中运行emacs可以通过comint将命令发送到powershell并读取响应。因此emacs可以快速获得反射结果。)
当代码用于var
完成事物的声明时,问题就来了。这意味着未明确指定类型,并且补全将不起作用。
使用var
关键字声明变量时,如何可靠地确定使用的实际类型?只是要清楚一点,我不需要在运行时确定它。我想在“设计时”确定它。
到目前为止,我有这些想法:
- 编译并调用:
- 提取声明语句,例如`var foo =“ a string value”;`
- 连接语句`foo.GetType();`
- 动态将生成的C#片段编译为新程序集
- 将程序集加载到新的AppDomain中,运行片段并获取返回类型。
- 卸下并丢弃组件
我知道该怎么做。但是,对于编辑器中的每个完成请求,这听起来都是非常沉重的。
我想我不需要每次都需要新的AppDomain。我可以将单个AppDomain重复用于多个临时程序集,并在多个完成请求中分摊设置和拆除它的成本。这更多是对基本概念的调整。
- 编译并检查IL
只需将声明编译到模块中,然后检查IL,以确定编译器推断出的实际类型。这怎么可能?我将用什么来检查IL?
还有更好的主意吗?注释?建议?
编辑 -对此进行进一步的考虑是不可接受的,因为调用可能会产生副作用。因此,必须排除第一个选项。
另外,我想我不能假设存在.NET 4.0。
更新 -上面没有提到的正确答案,但由埃里克·利珀特(Eric Lippert)轻轻指出,是实现完整的保真类型推断系统。这是在设计时可靠地确定var类型的唯一方法。但是,这也不容易做到。因为我没有幻想要尝试构建这样的东西,所以我选择了选项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语法。
在考虑通过绝对的“有限的设计”(礼貌用语“ hack”)解决这些问题之前,我必须考虑这些事情的价值。
解决依赖于方法参数或实例方法的问题的方法是,在生成,编译和IL分析的代码片段中,用相同类型的“合成”局部变量替换对这些事物的引用。
另一个更新 -完成依赖实例成员的var上的工作。
我所做的是(通过语义)询问类型,然后为所有现有成员生成综合替代成员。对于这样的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);
}
}
}
框架代码中提供了所有实例和静态类型成员。它编译成功。在这一点上,通过反射很容易确定局部变量的类型。
使之成为可能的原因是:
- 在emacs中运行powershell的能力
- C#编译器确实非常快。在我的机器上,编译内存中的程序集大约需要0.5s。击键之间的分析速度不够快,但足以支持按需生成完成列表。
我还没有研究LINQ。
这将是一个更大的问题,因为emacs对于C#具有语义词法分析器/解析器,而不是“执行” LINQ。