var
声明正在完成的内容时。这意味着类型没有明确指定,因此无法完成。当使用"var"关键字声明变量时,我该如何可靠地确定实际使用的类型?需要明确的是,我不需要在运行时确定它。我想在"设计时间"确定它。
到目前为止,我有以下想法:
1.编译和调用:
- 提取声明语句,例如 `var foo = "a string value";` - 连接语句 `foo.GetType();` - 动态编译生成的C#片段成一个新程序集 - 将程序集加载到新的AppDomain中,运行片段并获取返回类型。 - 卸载和丢弃程序集
我知道如何做到这一点。但是,对于编辑器中的每个完成请求,这听起来非常沉重。
我想我不需要每次都使用全新的AppDomain。我可以为多个临时程序集重复使用单个AppDomain,并将设置和拆卸的成本分摊到多个完成请求中。这只是基本想法的微调。
2.编译和检查IL
只需将声明编译成模块,然后检查IL以确定编译器推断出的实际类型。这可能吗?我要用什么来检查IL?
有更好的想法吗?评论?建议?
编辑 - 经过进一步思考,编译和调用不可接受,因为调用可能会产生副作用。因此,第一个选项必须被排除。
此外,我认为我不能假设 .NET 4.0 的存在。
更新 - 正确答案并未提及,但Eric Lippert轻轻地指出,即实现完全保真度类型推断系统。 这是可靠地在设计时确定var类型的唯一方法。 但是,这也不容易。 因为我没有幻想要尝试构建这样的东西,所以我选择了选项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检查必须找到第三个本地变量的类型,而不是第一个。
还有这个:
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分析代码片段时,将对这些内容的引用替换为相同类型的“合成”本地变量。
另一个更新 - 现在可以完成依赖于实例成员的变量。
我的做法是通过语义来查询类型,然后为所有现有成员生成合成替代成员。对于像这样的 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。那将是一个更大的问题,因为emacs对C#的语义分析器/解析器不支持LINQ。