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

时间:2010-04-04 19:57:58

标签: c# .net

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

这样的var类型
private void myMethod(var myValue) {
   // do something
}

9 个答案:

答案 0 :(得分:9)

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

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

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

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

答案 1 :(得分:7)

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

var s = "hello";

此处确定为Foo

var foo = new Foo();

在方法参数中,没有“赋值的右侧”,所以你不能使用var。

答案 2 :(得分: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)
}

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

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

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

一般而言,语言语法和语义的细微变化可能会将“确定性”类型推理算法变为不可判断的算法。

答案 3 :(得分:7)

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

  

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

答案 4 :(得分:6)

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

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

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())可能会强制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) { ... }
}

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支持类型推断很好因为它们有这些类型的限制:你不能在声明方法之前调用它们,第一个方法调用推断函数确定类型,编译顺序对类型推断有影响等。

答案 5 :(得分:1)

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

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

这里,在方法参数中,编译器无法弄清楚你的真正含义。怎么样?你究竟是什么意思?编译器无法从方法定义中推断出类型 - 因此它不是有效的语句。

答案 6 :(得分:1)

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

答案 7 :(得分:0)

在C#4中查看dynamic

答案 8 :(得分:0)

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

如果编译器执行全局类型推断,则C#可以执行此操作,但它不会。

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

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