Linq implicity类型的范围变量

本文关键字:范围 变量 类型 implicity Linq | 更新日期: 2023-09-27 18:28:32

使用Linq,范围变量(e)可以从它所来自的数组/集合(emps)中隐式键入,但foreach语句在没有var关键字或类型的情况下不能做同样的事情。为什么会这样?

在ex1中,编译器知道e的类型是Employee,而没有给出var关键字或任何东西。为什么ex2中的foreach循环不能做同样的事情,你必须提供类型(无论是var还是某种类型)。

ex1.

    Employee[] emps = {new Employee ( 1, "Daniel", "Cooley", 7, 57.98M };
    public void SortByLastname()
    {
      var sortedByLastname =
            from e in emps
            orderby e.LastName
            select e.FirstName;
    }

ex2.

        foreach (Employee empl in emps)
        {
            Console.WriteLine("Employee " + empl);
        }

这可能是过度分析,但我正在努力弄清为什么会出现这种情况。

答案很可能是Linq查询语法设置为自动推断范围变量的类型,而foreach语句则不是。有人能解释一下为什么会这样吗?

Linq implicity类型的范围变量

更新:这个问题是我2012年6月25日博客的主题。谢谢你的提问!


有了Linq,range变量可以从它来自的集合中隐式键入,但foreach语句在没有var关键字的情况下不能做同样的事情。

这是正确的。

为什么会这样?

我从来不知道如何回答"为什么"的问题。所以我假设你问了一个不同的问题:

命名变量可以通过两种不同的方式隐式类型化。命名的局部变量、for循环变量、foreach循环变量或using语句变量可以通过用"var"替换其显式类型来隐式键入。lambda参数或查询范围变量可以通过完全省略其类型来隐式键入。

正确。

这是不一致的。一个基本的设计原则是避免不一致,因为它令人困惑;用户自然地认为不一致性传达了含义。这些特征是否一致?

事实上,有两种方法可以使它们保持一致。第一个是在任何地方都需要"var",这样你就会说:

Func<double, double> f = (var x)=>Math.Sin(x);
var query = from var customer in customers
            join var order in orders on customer.Id equals ...

所有的设计都是一系列的妥协。这符合一致性测试,但现在感觉笨拙而冗长。

第二个是在所有地方消除"var",这样你就会说:

x = 12; // Same as "int x = 12;"
using(file = ...) ... 
for(i = 0; i < 10; ++i) ...
foreach(c in customers) ... 

在前三种情况下,我们现在无意中添加了"隐式声明的局部变量"的功能,而不是"隐式类型局部变量"。仅仅因为给一个以前没有使用过的名称分配了一些东西,就声明了一个新的局部变量,这似乎很奇怪,也不像C#。这是我们在像JScript或VBScript这样的语言中所期望的功能,而不是C#。

然而,在foreach块中,从上下文中可以清楚地看到,正在引入一个局部变量。我们可以在不引起太多混乱的情况下消除"var",因为"in"不会被误认为是赋值。

好的,让我们总结一下我们可能的功能:

  • 特性1:处处都需要var
  • 特性2:无需var
  • 特性3:对于循环和using,局部变量需要var,但对于foreach循环、lambdas或范围变量则不需要
  • 特性4:对于使用和foreach的循环,局部变量需要var,但不需要lambdas或range变量

前两者都有一致性的好处,但一致性只是一个因素。第一个是笨重的。第二个太过动态和混乱。第三个和第四个似乎是合理的妥协,尽管它们并不一致。

那么问题是:foreach循环变量更像局部变量还是更像lambda参数?显然,它更像是一个局部变量;事实上,foreach循环被指定为重写,其中循环变量变为局部变量。为了与"For"循环的一致性,以及与foreach循环的C#1.0和C#2.0使用的一致性(这需要某种类型),我们选择选项四作为优于选项三的选项。

我希望这能回答你的问题。如果没有,那就提出一个更具体的问题。

您不需要列出类型的原因是因为它(基本上)被分解为扩展方法。您应该能够将ex2重写为:

emps.ForEach(empl=>Console.WriteLine("Employee " + empl);

请注意,您不需要显式地说出类型,因为它是从emps 推断出来的

因此ex1将分解为:

emps.OrderBy(e=>e.LastName).Select(e=>e.FirstName);

为了更全面地理解这一点,我真的建议购买Jon Skeets的书《C#深度第二版》

p>在第二个例子中,"empl"看起来像是一个字符串,因为LINQ语句的select子句说e.FirstName。如果你想让它包括员工列表,请使用以下内容:
var sortedByLastname =
  from e in emps
  select e
  orderby e.LastName;

这与语言构造有关,而不是变量类型推断。foreach循环在没有var关键字的情况下不能执行相同操作的原因是,foreach(var empl in list)foreach(empl in list)的含义不同(我甚至不确定第二个是否合法)。第一个分配一个新变量,而第二个(如果合法)将使用一个已经存在的变量(原因的变量必须正确键入)。但是,LINQ中的for构造是以这样一种方式生成的,即当您执行for e in list时,e将被创建为一个变量。