为什么不能将方法参数声明为var类型

won*_*nde 4 .net c#

我想知道为什么方法参数不可能像var类似

private void myMethod(var myValue) {
   // do something
}
Run Code Online (Sandbox Code Playgroud)

Mar*_*ers 9

您只能var在方法体内使用变量.此外,必须在声明处指定变量,并且必须能够从右侧的表达式明确推断出类型.

在所有其他地方,您必须指定一种类型,即使理论上可以推断出类型.

原因是编译器的设计方式.简化的描述是它首先解析除方法体之外的所有内容,然后对每个类,成员等的静态类型进行全面分析.然后在解析方法体时使用此信息,特别是用于推导本地类型变量声明为var.如果var允许在任何地方,则需要对编译器的工作方式进行大的更改.

您可以阅读Eric Lippert关于此主题的文章了解更多详情:


Wim*_*nen 7

因为编译器通过查看赋值的右侧来确定实际类型.例如,这里确定为字符串:

var s = "hello";
Run Code Online (Sandbox Code Playgroud)

这里决定Foo:

var foo = new Foo();
Run Code Online (Sandbox Code Playgroud)

在方法参数中,没有"赋值的右侧",因此您不能使用var.


Pau*_*rth 7

请查看朱丽叶的答案,以便更好地回答这个问题.

因为向C#添加完整类型推断太难了.其他语言(如Haskell和ML)可以自动推断出最常见的类型而无需声明它.

其他答案表明编译器推断var的类型是"不可能的",但实际上它原则上是可能的.例如:

abstract void anotherMethod(double z, double w);

void myMethod<T>(T arg)
{
    anotherMethod(arg, 2.0); // Now a compiler could in principle infer that arg must be of type double (but the actual C# compiler can't)
}
Run Code Online (Sandbox Code Playgroud)

"var"方法参数原则上与泛型方法相同:

void myMethod<T>(T arg)
{
    ....
}
Run Code Online (Sandbox Code Playgroud)

不幸的是,你不能只为两者使用相同的语法,但这可能是因为C#的类型推断仅在以后添加.

通常,语言语法和语义的细微变化可以将"确定性"类型推断算法变为不可判断的算法.


Mic*_*tum 7

请参阅Eric Lippert发布​​的关于为什么不允许在字段上使用var的内容,其中还包含解释为什么它在方法签名中不起作用的说明:

让我简单地过分简化C#编译器的工作原理.首先,我们遍历每个源文件并执行"仅限顶级"解析.也就是说,我们在所有嵌套级别标识每个命名空间,类,结构,枚举,接口和委托类型声明.我们解析所有字段声明,方法声明等.事实上,我们解析除方法体之外的所有事物; 那些,我们稍后跳过并回到他们身边.
[...]
如果我们有"var"字段,则在分析表达式之前无法确定字段的类型,并且在我们已经需要知道字段的类型之后发生这种情况.


Jul*_*iet 6

ML,Haskell,Scala,F#,SML和其他语言可以很容易地从他们自己语言中的等效表达式中找出类型,主要是因为它们从一开始就考虑了类型推理.C#不是,它的类型推断被作为访问匿名类型问题的事后解决方案.

我推测真正的Hindley-Milner类型推断从未实现过C#,因为它很难推断出依赖于类和继承的语言中的类型.假设我有以下课程:

class Base { public void Print() { ... } }

class Derived1 : Base { }

class Derived2 : Base { }
Run Code Online (Sandbox Code Playgroud)

现在我有这个方法:

var create() { return new Derived1(); }
Run Code Online (Sandbox Code Playgroud)

什么是返回类型?是Derived1,还是应该Base?就此而言,它应该是object吗?

好的,现在让我说我有这个方法:

void doStuff(var someBase) { someBase.Print(); }

void Main()
{
    doStuff(new Derived1());
    doStuff(new Derived2()); // <-- type error or not?
}
Run Code Online (Sandbox Code Playgroud)

第一次打电话,doStuff(new Derived1())可能是强迫doStuff这种类型doStuff(Derived1 someBase).我们现在假设我们推断出具体类型而不是泛型类型T.

第二次电话怎么样doStuff(new Derived1())?这是一个类型错误,还是我们推广到了doStuff<T>(T somebase) where T : Base?如果我们在一个单独的,未引用的程序集中进行相同的调用会怎么样 - 类型推断算法不知道是使用窄类型还是更通用的类型.因此,我们最终会得到两种不同类型的签名,这些签名基于方法调用是来自程序集内部还是外部程序集.

您无法根据函数的用法推广更广泛的类型.一旦你知道传入哪种具体类型,你基本上需要解决一个具体的类型.所以在上面的示例代码中,除非你明确地转换为Base类型,否则doStuff被约束为接受类型Derived1和第二次调用是一个类型错误.

现在这里的诀窍是解决一个类型.这里发生了什么:

class Whatever
{
    void Foo() { DoStuff(new Derived1()); } 
    void Bar() { DoStuff(new Derived2()); }
    void DoStuff(var x) { ... }
}
Run Code Online (Sandbox Code Playgroud)

这是什么类型的DoStuff?就此而言,基于上述内容,我们知道其中一个Foo或多个Bar方法包含类型错误,但您能从中查看哪个有错误吗?

如果不改变C#的语义,就无法解析类型.在C#中,方法声明的顺序对编译没有影响(或者至少它不应该;)).您可能会说,首先声明的方法(在本例中为Foo方法)确定类型,因此Bar出错.

这有效,但它也改变了C#的语义:方法顺序的更改将改变方法的编译类型.

但是,让我们说我们走得更远:

// Whatever.cs
class Whatever
{
    public void DoStuff(var x);
}

// Foo.cs
class Foo
{
    public Foo() { new Whatever().DoStuff(new Derived1()); }
}

// Bar.cs
class Bar
{
    public Bar() { new Whatever().DoStuff(new Derived2()); }
}
Run Code Online (Sandbox Code Playgroud)

现在,这些方法正在从不同的文件中调用.这是什么类型的?如果不对编译顺序强加一些规则就不可能做出决定:如果在Bar.cs之前编译Foo.cs,则类型由Foo.cs确定.

虽然我们可以在C#上强加这些规则来进行类型推理,但它会彻底改变语言的语义.

相比之下,ML,Haskell,F#和SML支持类型推断很好,因为它们有这些类型的限制:你不能在声明方法之前调用方法,对推断函数的第一个方法调用决定了类型,编译顺序有对类型推断等的影响