65.9K
CodeProject 正在变化。 阅读更多。
Home

LINQ:.NET 语言集成查询

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.53/5 (8投票s)

2007年8月27日

CPOL

5分钟阅读

viewsIcon

23972

.NET Framework 中新增的通用查询功能、LINQ、Lambda 表达式、扩展方法

目录

本系列第一部分将讨论以下内容:

  1. 引言
  2. 支持 LINQ 的语言特性
    1. 扩展方法
    2. Lambda 表达式
    3. 局部变量类型推断
    4. 匿名类型
    5. 对象初始化

引言

那么,我们直接开始写代码吧。

int[] array = new int[] { 1, 2, 3,
4, 5, 6, 7, 8, 9, 10 };
var evenNumbers = from a in array
          where a % 2 == 0
          select a;
ObjectDumper.Write(evenNumbers);

上面声明了一个整数数组,并且只打印偶数。

以上代码的输出如下

2

4

6

8

10 

ObjectDumper 是一个使用反射并进行 Console.WriteLine() 操作的实用类。ObjectDumper 是一个智能类,它可以接受任何对象,使用反射推断其类型,并将值打印到流中。默认情况下,流是控制台。因此,如果我有一个名为 Customer 的类,并且它有一个名为 Namepublic 字段,然后我将该类的对象传递给 ObjectDumper,它将以 Name=<customerName> 的方式打印。如果我传递一个集合,它会遍历集合中的所有项并打印它们各自的值。这个类也可以用于 .NET 2.0。

现在,让我们看看这段代码在 C# 编译器眼中是什么样的。架构师称这段代码为语法糖。如果你看这段代码,没有方法调用,没有对象,也没有 "." 运算符。

一旦 C# 编译器看到上面的代码,它就会将其翻译成如下所示:

var evenNumbers = array.Where(a => 0 == a % 2).Select(a => a); 

现在,作为一个开发者,我很高兴看到一些对象和一个方法调用。但我仍然感到困惑,因为一个 int 数组没有 WhereSelect 方法。微软是否为这个类型添加了这些新函数?而 "a => 0 == a % 2" 是什么意思?

扩展方法

WhereSelect 是扩展方法。 .NET 3.5 构建在 .NET 2.0 之上,它允许开发者在现有类/类型中添加他/她自己的函数。但是添加扩展方法有一些规则。你只能从 static 类中为类型添加方法。这个 static 类应该包含一个 static 方法,该方法将被调用为该类型的实例方法。让我们以一个例子来说明我的意思:

public static class MyExtensions
    {
        public static string Reverse(this string str)
        {
            StringBuilder sb = new StringBuilder(str.Length);
            for (int i = str.Length - 1; i >= 0; i--)
                sb.Append(str[i]);
            return sb.ToString();
        }
    }

在这里,我们向 String 类添加了一个 Reverse 函数。现在让我们看看如何使用这个函数:

string s = "Hello LINQ";
Console.WriteLine(s.Reverse()); //will print "QNIL olleH"

注意事项

  1. Reverse 函数是一个 static 函数,被调用为实例函数。
  2. Reverse 函数接受一个 string 作为参数,而函数调用是 void
  3. 注意函数有 "this" 作为其第一个参数。

当编译器看到对一个不是该类型成员函数的调用时,它会查找扩展方法并调用最匹配的函数。这意味着如果我有一个另一个命名空间,其中 Reverse 作为扩展方法,编译器将要么给出歧义错误,要么调用最接近函数调用的第一个函数。为了避免歧义错误,你可以显式地指定要调用的命名空间类和方法。在我们的例子中,当编译器看到这个调用时,它将其替换为:

Console.WriteLine(MyExtensions.Reverse(s)); 

Lambda 表达式

回到以下代码:

var even = array.Where(a => 0 == a % 2).Select(a => a);

现在我们知道 WhereSelect 是扩展方法。但是 "" 是什么意思?

对于 .NET 2.0 开发者来说,上面的代码相当于下面的代码:

var e = array.Where(delegate(int a)
	{ return 0 == a % 2; }).Select(delegate(int a) {return a;});

Where 扩展方法如下所示:

public delegate TR Func<T0, TR>(T0 a0);

public static IEnumerable<T> Where<T>(this IEnumerable<T> source,
	Func<T, bool> predicate)
{
     if (source == null || predicate == null)
         throw new ArgumentNullException();
     foreach (T item in source)
         if(predicate(item))
           yield return item;
}

这个扩展方法表示它适用于所有 IEnumerable<T> 类型。它遍历所有项,只返回满足给定条件(谓词)的那些类型。

现在让我们看看 "Where(a => 0 == a % 2).Select(a => a);"。箭头(=>)运算符被引入,整个表达式被称为 lambda 表达式。让我们看更多关于 lambda 表达式的例子:

public delegate T Func<A0, A1, T>(A0 arg0, A1 arg1);

Func<int, int, int> f = (x, y) => x * y;
ObjectDumper.Write(f(5, 6)); 

上述代码的输出是 30

编译器所做的是推断 xy 的类型,使其类型安全,然后创建一个匿名的委托函数并调用它。这对面向对象的人来说更有意义。

局部变量类型推断

再次回到主要查询:

var evenNumbers = from a in array
                 where a % 2 == 0
                  select a;

如果你还没有注意到,查询的输出被赋给了 var。这个 var 在 Jscript 中不对应于对象,在 Jscript 中它表示一个对象。在 C# 中,它的类型在赋值时被推断出来。你不能这样做:

var unknowType = null; 

这将导致编译错误。

那么 var 为什么重要呢?如果你看微软提供的绝大多数扩展方法,它们都返回 IEnumerable<T>。所以,在你的代码中,每一次写查询,你也要写另一个封装它的类。这不是个好主意。所以你可以暂时使用 var 变量。考虑到这一点,你不能将 var 作为函数参数传递。因此,如果你想在函数外部使用查询结果,你必须定义自己的类型,除非你选择整个项。

匿名类型

这次我们来看一个不同的查询:

var contacts = from c in customers
           where c.State == "WA"
           select new { c.Name, c.Phone }; 

如果你看 select 语句,这里我们创建了一个全新的类型,甚至没有指定类型名称。在这种情况下,编译器将创建一个新类型,它将有两个 public 字段,并且字段的类型将从源类型推断出来。

注意,由于我们没有指定类型名称,我们将无法重用该类型。并且该类型的范围是它被使用的函数。一旦编译器创建了类型,它将使用源的值来初始化字段。

对象初始化

回到同一个查询:

var contacts = from c in customers
           where c.State == "WA"
           select new { c.Name, c.Phone }; 

在这种情况下,编译器创建了一个匿名类型,但它没有创建接受两个参数然后初始化其字段的构造函数。那么字段是如何初始化的呢?

让我们看另一个例子,这将使事情更清楚:

public class Point
{
    private int x, y;
    public int X { get { return x; } set { x = value; } }
    public int Y { get { return y; } set { y = value; } }
}
Point a = new Point { X = 0, Y = 1 }; 

现在这段代码就像写成这样一样:

Point a = new Point();
a.X = 0;
a.Y = 1;

我想代码不言自明。

未来文章

  • 本系列第二部分将讨论 LINQ to SQL。
  • 本系列第三部分将讨论 LINQ to XML。

如需更多详情,您可以发送邮件至 SumitkJain@hotmail.com 与我联系。

历史

  • 2007 年 8 月 27 日:初始发布
© . All rights reserved.