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

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

private void myMethod(var myValue) { // do something } 

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

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

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

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

  • 为什么字段上没有var?

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

 var s = "hello"; 

在这里它被确定为Foo

 var foo = new Foo(); 

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

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

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

请参阅朱丽叶的回答,以便更好地回答这个问题。

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

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

 abstract void anotherMethod(double z, double w); void myMethod(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) } 

“var”方法参数原则上与generics方法相同:

 void myMethod(T arg) { .... } 

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

通常,语言语法和语义的细微变化可以将“确定性”类型推断算法变成不可判定的算法。

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

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

 class Base { public void Print() { ... } } class Derived1 : Base { } class Derived2 : Base { } 

现在我有这个方法:

 var create() { return new Derived1(); } 

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

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

 void doStuff(var someBase) { someBase.Print(); } void Main() { doStuff(new Derived1()); doStuff(new Derived2()); // <-- type error or not? } 

第一个调用doStuff(new Derived1()) ,可能强制doStuffdoStuff(Derived1 someBase)类型doStuff(Derived1 someBase) 。 我们现在假设我们推断出一个具体类型而不是generics类型T

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

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

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

 class Whatever { void Foo() { DoStuff(new Derived1()); } void Bar() { DoStuff(new Derived2()); } void DoStuff(var x) { ... } } 

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

如果不改变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()); } } 

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

虽然我们可以在C#上强加那些规则来使类型推理工作,但它会彻底改变语言的语义。

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

“var”关键字在C#和VB.NET中用于类型推断 – 你基本上告诉C#编译器:“你弄清楚类型是什么”。

“var”仍然是强类型的 – 你自己也懒得写出类型并让编译器弄清楚 – 基于赋值右侧的数据类型。

在这里,在方法参数中,编译器无法确定您的真正含义。 怎么样? 你到底是什么类型的? 编译器无法从方法定义中推断出类型 – 因此它不是有效的语句。

因为c#是类型安全且类型的语言。 在程序的任何地方,编译器始终知道您正在使用的参数类型。 var关键字刚刚被引入以具有匿名类型的变量。

检查C#4中的dynamic

类型推断是类型推断,可以是局部表达式,也可以是全局/过程间。 所以它不是“没有右手边”,因为在编译器理论中,过程调用是一种“右手边”。

如果编译器执行全局类型推断,C#可以执行此操作,但事实并非如此。

如果你想要一个接受任何东西的参数,你可以使用“object”,但是你需要自己处理运行时转换和潜在exception。

C#中的“var”不是运行时类型绑定,它是一个以非常特定的类型结束的编译时function,但C#类型推断的范围有限。