介绍 LINQ—语言集成查询
学习LINQ的基础知识,即数据类型var、匿名类型、lambda表达式、LINQ语法和LINQ查询。
引言
在我之前在CodeProject.com上发表的三篇文章中,我解释了Windows Communication Foundation (WCF)的基础知识,包括
如果你仔细阅读了那三篇文章,你应该能够使用WCF了。在最近的两篇文章中,我解释了如何将LINQ和实体框架与WCF结合使用,所以现在你也应该能够使用LINQ和EF了。但是,仅仅阅读那两篇文章,你可能无法完全理解LINQ和EF。所以,我来了,为你解释LINQ和EF的所有基础知识。
除了LINQ和EF之外,有些人可能仍然在使用LINQ to SQL,它是微软的第一个ORM产品,或者是C#团队的副产品,或者是EF的简化版,或者你认为它是什么就是什么。由于LINQ to SQL (L2S)非常易于使用,我还会写一些文章来解释它。
话虽如此,将来我将撰写以下五篇文章来解释LINQ、LINQ to SQL和EF
- 介绍LINQ-语言集成查询(本文)
- LINQ to SQL:基本概念和功能(下一篇文章)
- LINQ to SQL:高级概念和功能(未来文章)
- LINQ to Entities:基本概念和功能(未来文章)
- LINQ to Entities:高级概念和功能(未来文章)
完成这五篇文章后,我将回来撰写更多关于WCF的实际工作经验文章,如果您现在正在使用WCF,这些文章肯定会对您的实际工作有所帮助。
在本文中,我将涵盖以下主题
- 什么是LINQ
- 新数据类型
var
- 自动属性
- 对象初始化器和集合初始化器
- 匿名类型
- 扩展方法
- Lambda 表达式
- 内置LINQ扩展方法和方法语法
- LINQ查询语法和查询表达式
- 内置LINQ运算符
什么是LINQ
语言集成查询(LINQ)是.NET Framework的一组扩展,包含语言集成的查询、集合和转换操作。它通过原生语言语法扩展了C#和Visual Basic的查询功能,并提供了类库以利用这些功能。
让我们先看一个例子。假设有一个整数列表,如下所示
List<int> list = new List<int>() { 1, 2, 3, 4, 5, 6, 100 };
要找出此列表中的所有偶数,您可能会编写如下代码
List<int> list1 = new List<int>();
foreach (var num in list)
{
if (num % 2 == 0)
list1.Add(num);
}
现在使用 LINQ,您可以从该列表中选择所有偶数,并将查询结果赋给一个变量,只需一句话,如下所示
var list2 = from number in list
where number % 2 == 0
select number;
在这个例子中,`list2` 和 `list1` 是等价的。`list2` 包含与 `list1` 相同的数字。正如你所看到的,你没有编写 `foreach` 循环。相反,你编写了一个 SQL 语句。
但是这里的 `from`、`where` 和 `select` 是什么意思?它们在哪里定义?如何以及何时使用它们?现在我们开始探索。
创建测试解决方案和项目
为了展示这些与LINQ相关的新特性,我们需要一个测试项目来演示它们是什么以及如何使用它们。所以我们首先需要创建测试解决方案和项目。
按照以下步骤创建解决方案和项目
- 启动 Visual Studio 2010。
- 选择菜单选项“文件 | 新建 | 项目...”以创建新解决方案。
- 在“新建项目”窗口中,选择“Visual C# | 控制台应用程序”作为模板。
- 输入 TestLINQ 作为解决方案名称,TestNewFeaturesApp 作为(项目)名称。
- 点击“确定”创建解决方案和项目。
新数据类型 var
LINQ 最重要的第一个新特性是新数据类型 `var`。这是一个新关键字,可用于声明变量,并且该变量可以初始化为任何有效的 C# 数据。
在 C# 3.0 规范中,此类变量称为隐式类型局部变量。
一个 `var` 变量在声明时必须初始化。初始化表达式的编译时类型不能为 `null` 类型,但运行时表达式可以为 `null`。一旦初始化,其数据类型就固定为初始数据的类型。
以下语句是 `var` 关键字的有效用法
// valid var statements
var x = "1";
var n = 0;
string s = "string";
var s2 = s;
s2 = null;
string s3 = null;
var s4 = s3;
在编译时,上述 `var` 语句将编译成 IL,如下所示
string x = "1";
int n = 0;
string s2 = s;
string s4 = s3;
`var` 关键字只对 Visual Studio 编译器有意义。编译后的程序集实际上是一个有效的 .NET 2.0 程序集。它不需要任何特殊的指令或库来支持此功能。
以下语句是 `var` 关键字的无效用法
// invalid var statements
var v;
var nu = null;
var v2 = "12"; v2 = 3;
第一个是非法的,因为它没有初始化器。
第二个将变量 `nu` 初始化为 `null`,这是不允许的,尽管一旦定义,`var` 类型变量可以被赋值为 `null`。如果你认为在编译时,编译器需要使用这种类型的初始化器创建一个变量,那么你就会明白为什么初始化器在编译时不能为 `null`。
第三个是非法的,因为一旦定义,整数不能隐式转换为字符串(`v2` 的类型是 `string`)。
自动属性
过去,对于一个类成员,如果我们想将其定义为属性成员,我们必须首先定义一个私有成员变量。例如,对于 `Product` 类,我们可以将属性 `ProductName` 定义如下
private string productName;
public string ProductName
{
get { return productName; }
set { productName = value; }
}
如果我们需要在get/set方法中添加一些逻辑,这可能很有用。但是,如果不需要,上面的格式会变得很繁琐,尤其是在成员很多的情况下。
现在,使用 C# 3.0 及更高版本,上述属性可以用一个语句简化
public string ProductName { get; set; }
当 Visual Studio 编译此语句时,它将自动创建一个私有成员变量 `productName`,并使用旧式的 get/set 方法定义该属性。这可以节省大量的输入。
正如新类型 `var` 一样,自动属性只对 Visual Studio 编译器有意义。编译后的程序集实际上是一个有效的 .NET 2.0 程序集。
有趣的是,稍后,如果你发现你需要向 get/set 方法添加逻辑,你仍然可以将此自动属性转换为旧式属性。
现在,让我们在测试项目中创建这个类
public class Product
{
public int ProductID { get; set; }
public string ProductName { get; set; }
public decimal UnitPrice { get; set; }
}
我们可以将这个类放在 `Program.cs` 文件中,位于 `TestNewFeaturesApp` 命名空间内。我们将在本文中一直使用这个类,以测试与 LINQ 相关的 C# 特性。
对象初始化器
过去,我们无法在不使用构造函数的情况下初始化对象。例如,如果 `Product` 类有一个带三个参数的构造函数,我们可以像这样创建和初始化一个 `Product` 对象
Product p = new product(1, "first candy", 100.0);
或者,我们可以创建对象,然后稍后初始化,像这样
Product p = new Product();
p.ProductID = 1;
p.ProductName = "first candy";
p.UnitPrice=(decimal)100.0;
现在有了新的对象初始化器功能,我们可以这样做
Product product = new Product
{
ProductID = 1,
ProductName = "first candy",
UnitPrice = (decimal)100.0
};
在编译时,编译器将自动插入必要的属性设置器代码。所以,同样,这个新功能是 Visual Studio 编译器功能。编译后的程序集实际上是一个有效的 .NET 2.0 程序集。
我们还可以像这样定义和初始化一个数组变量
var arr = new[] { 1, 10, 20, 30 };
这个数组被称为隐式类型数组。
集合初始化器
与对象初始化器类似,我们也可以在声明集合时对其进行初始化,如下所示
List<Product> products = new List<Product> {
new Product {
ProductID = 1,
ProductName = "first candy",
UnitPrice = (decimal)10.0 },
new Product {
ProductID = 2,
ProductName = "second candy",
UnitPrice = (decimal)35.0 },
new Product {
ProductID = 3,
ProductName = "first vegetable",
UnitPrice = (decimal)6.0 },
new Product {
ProductID = 4,
ProductName = "second vegetable",
UnitPrice = (decimal)15.0 },
new Product {
ProductID = 5,
ProductName = "another product",
UnitPrice = (decimal)55.0 }
};
这里,我们创建了一个列表并用五个新产品对其进行了初始化。对于每个新产品,我们都使用了对象初始化器来初始化其值。
与对象初始化器一样,集合初始化器这个新功能也是Visual Studio编译器的功能,并且编译后的程序集是一个有效的.NET 2.0程序集。
匿名类型
借助对象初始化器的新特性和新的 `var` 数据类型,我们可以轻松地在 C# 3.0 中创建匿名数据类型。
例如,如果我们像这样定义一个变量
var a = new { Name = "name1", Address = "address1" };
在编译时,编译器实际上会创建一个匿名类型,如下所示
class __Anonymous1
{
private string name;
private string address;
public string Name {
get{
return name;
}
set {
name=value
}
}
public string Address {
get{
return address;
}
set{
address=value;
}
}
}
匿名类型的名称由编译器自动生成,不能在程序文本中引用。
如果两个匿名类型在它们的初始化器中具有相同成员和相同数据类型,那么这两个变量具有相同的类型。例如,如果定义了另一个变量,如下所示
var b = new { Name = "name2", Address = "address2" };
然后我们可以像这样将 `a` 赋值给 `b`
b = a;
当 LINQ 的结果可以随心所欲地塑造时,匿名类型对于 LINQ 尤其有用。在讨论 LINQ 时,我们将给出更多此类示例。
如前所述,这个新功能仍然是 Visual Studio 编译器功能,编译后的程序集是一个有效的 .NET 2.0 程序集。
扩展方法
扩展方法是可以使用实例方法语法调用的静态方法。实际上,扩展方法使我们能够通过附加方法扩展现有类型和构造类型。
例如,我们可以这样定义一个扩展方法
public static class MyExtensions
{
public static bool IsCandy(this Product p)
{
if (p.ProductName.IndexOf("candy") >= 0)
return true;
else
return false;
}
}
在这个例子中,静态方法 `IsCandy` 接受一个 `this` `Product` 类型参数,并在产品名称中搜索单词 `candy`。如果找到匹配项,它假定这是一个糖果产品并返回 `true`。否则,它返回 `false`,表示这不是一个糖果产品。
由于所有扩展方法都必须在顶层静态类中定义,为了简化示例,我们将此类放在与我们的主测试应用程序 TestNewFeaturesApp 相同的命名空间中,并使此类与 `Program` 类处于同一级别,以便它成为顶层类。现在,在程序中,我们可以像这样调用此扩展方法
if (product.IsCandy())
Console.WriteLine("yes, it is a candy");
else
Console.WriteLine("no, it is not a candy");
看起来 `IsCandy` 是 `Product` 类的真实实例方法。实际上,它是 `Product` 类的真实方法,但它没有在 `Product` 类中定义。相反,它在另一个静态类中定义,以扩展 `Product` 类的功能。这就是为什么它被称为扩展方法。
它不仅看起来像一个真实的实例方法,而且当在产品变量后输入点时,这个新的扩展方法实际上会弹出。下图显示了 Visual Studio 中 `product` 变量的智能感知。
在 Visual Studio 的底层,当一个实例上的方法调用被编译时,编译器首先检查该类中是否存在该方法的实例方法。如果没有匹配的实例方法,它会查找导入的静态类,或同一命名空间中的任何静态类。它还会搜索第一个参数与实例类型相同(或实例类型的超类型)的扩展方法。如果找到匹配项,编译器将调用该扩展方法。这意味着实例方法优先于扩展方法,并且在内部命名空间声明中导入的扩展方法优先于在外部命名空间中导入的扩展方法。
在我们的例子中,当 `product.IsCandy()` 被编译时,编译器首先检查 `Product` 类,但没有找到名为 `IsCandy` 的方法。然后它搜索静态类 `MyExtensions`,并找到一个名为 `IsCandy` 且第一个参数类型为 `Product` 的扩展方法。
在编译时,编译器实际上将 `product.IsCandy()` 更改为此调用
MyExtensions.IsCandy(product)
令人惊讶的是,扩展方法可以为 `sealed` 类定义。在我们的示例中,您可以将 `Product` 类更改为 `sealed`,它仍然可以正常运行。这为我们扩展系统类型提供了极大的灵活性,因为许多系统类型都是 `sealed` 的。另一方面,扩展方法的发现性较差,维护起来也更困难,因此应谨慎使用。如果您的需求可以通过实例方法实现,则不应定义扩展方法来完成相同的工作。
不出所料,这个新功能仍然是 Visual Studio 编译器功能,编译后的程序集是一个有效的 .NET 2.0 程序集。
扩展方法是LINQ的基础。稍后我们将讨论.NET 3.5在`System.Linq`命名空间中定义的各种扩展方法。
现在,`Program.cs` 文件应该像这样
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace TestNewFeaturesApp
{
class Program
{
static void Main(string[] args)
{
// valid var statements
var x = "1";
var n = 0;
string s = "string";
var s2 = s;
s2 = null;
string s3 = null;
var s4 = s3;
/*
string x = "1";
int n = 0;
string s2 = s;
string s4 = s3;
*/
// invalid var statements
/*
var v;
var nu = null;
var v2 = "12"; v2 = 3;
*/
// old way to create and initialize an object
/*
Product p = new product(1, "first candy", 100.0);
Product p = new Product();
p.ProductID = 1;
p.ProductName = "first candy";
p.UnitPrice=(decimal)100.0;
*/
//object initializer
Product product = new Product
{
ProductID = 1,
ProductName = "first candy",
UnitPrice = (decimal)100.0
};
var arr = new[] { 1, 10, 20, 30 };
// collection initializer
List<Product> products = new List<Product> {
new Product {
ProductID = 1,
ProductName = "first candy",
UnitPrice = (decimal)10.0 },
new Product {
ProductID = 2,
ProductName = "second candy",
UnitPrice = (decimal)35.0 },
new Product {
ProductID = 3,
ProductName = "first vegetable",
UnitPrice = (decimal)6.0 },
new Product {
ProductID = 4,
ProductName = "second vegetable",
UnitPrice = (decimal)15.0 },
new Product {
ProductID = 5,
ProductName = "third product",
UnitPrice = (decimal)55.0 }
};
// anonymous types
var a = new { Name = "name1", Address = "address1" };
var b = new { Name = "name2", Address = "address2" };
b = a;
/*
class __Anonymous1
{
private string name;
private string address;
public string Name {
get{
return name;
}
set {
name=value
}
}
public string Address {
get{
return address;
}
set{
address=value;
}
}
}
*/
// extension methods
if (product.IsCandy()) //if(MyExtensions.IsCandy(product))
Console.WriteLine("yes, it is a candy");
else
Console.WriteLine("no, it is not a candy");
}
}
public sealed class Product
{
public int ProductID { get; set; }
public string ProductName { get; set; }
public decimal UnitPrice { get; set; }
}
public static class MyExtensions
{
public static bool IsCandy(this Product p)
{
if (p.ProductName.IndexOf("candy") >= 0)
return true;
else
return false;
}
}
}
到目前为止,在 `Program.cs` 中,我们已经
- 定义了几个 `var` 类型变量
- 定义了一个 `sealed` 类 `Product`
- 创建了一个名为“第一个糖果”的产品
- 创建了一个包含五种产品的产品列表
- 定义了一个静态类,并添加了一个静态方法 `IsCandy`,其 `this` 参数为 `Product` 类型,使此方法成为扩展方法
- 在糖果产品上调用扩展方法,并根据其名称打印出消息
如果你运行程序,输出将如下所示
Lambda 表达式
借助 C# 3.0 新的扩展方法功能和 C# 2.0 新的匿名方法(或内联方法)功能,Visual Studio 引入了一种称为 lambda 表达式的新表达式。
Lambda 表达式实际上是匿名方法的语法更改。它只是编写匿名方法的一种新方式。接下来,让我们逐步了解什么是 lambda 表达式。
首先,在 C# 3.0 中,有一个新的泛型委托类型 `Func`,它表示一个接受类型 `A` 参数并返回类型 `R` 值的函数
delegate R Func<A,R> (A Arg);
事实上,`Func` 有几个重载版本,其中 `Func` 是其中之一。
现在,我们将使用这种新的泛型委托类型来定义一个扩展
public static IEnumerable<T> Get<T>(this IEnumerable<T> source, Func<T, bool> predicate)
{
foreach (T item in source)
{
if (predicate(item))
yield return item;
}
}
这个扩展方法将应用于扩展 `IEnumerable` 接口的对象,并带有一个 `Func` 类型的参数,您可以将其视为指向函数的指针。这个参数函数是用于指定选择标准的谓词。这个方法将返回符合谓词标准的对象的列表。
现在我们可以创建一个新函数作为谓词
public static bool IsVege(Product p)
{
return p.ProductName.Contains("vegetable");
}
然后我们可以使用扩展方法 `Get` 来检索所有的蔬菜产品,像这样
var veges1 = products.Get(IsVege);
在前面的章节中,我们创建了一个产品列表,其中有五种产品,其中两种是蔬菜。因此,`veges1` 实际上是 `IEnumerable
Console.WriteLine("\nThere are {0} vegetables:", veges1.Count());
foreach (Product p in veges1)
{
Console.WriteLine("Product ID: {0} Product name: {1}",
p.ProductID, p.ProductName);
}
输出将是:
或者,我们可以先创建一个 `Func` 类型的新变量,将 `IsVege` 的函数指针赋值给这个新变量,然后将这个新变量传递给 `Get` 方法,像这样
Func<Product, bool> predicate = IsVege;
var veges2 = products.Get(predicate);
变量 `veges2` 将包含与 `veges1` 相同的产品。
现在,让我们使用 C# 2.0 匿名方法重写上述语句,它现在将变为
var veges3 = products.Get(
delegate (Product p)
{
return p.ProductName.Contains("vegetable");
}
);
此时,我们将谓词方法 `IsVege` 的主体放在扩展方法调用内部,并使用关键字 `delegate`。因此,为了从产品列表中获取蔬菜,我们不必定义特定的谓词方法。我们可以在需要时当场指定条件。
Lambda 表达式紧随上述步骤之后发挥作用。在 C# 3.0 中,使用 lambda 表达式,我们实际上可以编写以下一行语句来从产品列表中检索所有蔬菜
var veges4 = products.Get(p => p.ProductName.Contains("vegetable"));
在上面的语句中,`Get` 方法的参数是一个 lambda 表达式。第一个 `p` 是 lambda 表达式的参数,就像我们获取 `veges3` 时匿名方法中的参数 `p` 一样。这个参数是隐式类型的,在这种情况下,是 `Product` 类型,因为这个表达式应用于一个 `Products` 对象,其中包含一个 `Product` 对象的列表。这个参数也可以显式类型化,像这样
var veges5 = products.Get((Product p) => p.ProductName.Contains("vegetable"));
参数后跟 `=>` 标记,然后后跟一个表达式或一个语句块,它将是谓词。
所以,现在我们可以轻松地编写以下语句来获取所有糖果产品
var candies = products.Get(p => p.ProductName.Contains("candy"));
在编译时,所有 lambda 表达式都会根据 lambda 表达式转换规则转换为匿名方法。因此,同样,此功能仅是 Visual Studio 功能。我们不需要任何特殊的 .NET 运行时库或指令来运行包含 lambda 表达式的程序集。
简而言之,lambda 表达式只是以更简洁、函数式语法编写匿名方法的另一种方式。
内置LINQ扩展方法和方法语法
.NET Framework 3.5 在 `System.Linq` 命名空间中定义了许多扩展方法,包括 `Where`、`Select`、`SelectMany`、`OrderBy`、`OrderByDescending`、`ThenBy`、`ThenByDescending`、`GroupBy`、`Join` 和 `GroupJoin`。
我们可以像使用我们自己的扩展方法一样使用这些扩展方法。例如,我们可以使用 `Where` 扩展方法从 `Products` 列表中获取所有蔬菜,如下所示
var veges6 = products.Where(p => p.ProductName.Contains("vegetable"));
这将给我们带来与 `veges1` 到 `veges5` 相同的结果。
事实上,内置 LINQ 扩展方法 `Where` 的定义就像我们的扩展方法 `Get`,只是在不同的命名空间中
namespace System.Linq
{
public static class Enumerable
{
public static IEnumerable<T> Where<T>(this IEnumerable<T>
source, Func<T, bool> predicate)
{
foreach (T item in source)
{
if (predicate(item))
yield return item;
}
}
}
}
使用 LINQ 扩展方法的语句称为使用 LINQ 方法语法。
与我们前面章节中讨论的其他 C# 3.0 新特性不同,这些 LINQ 特定的扩展方法是在 .NET Framework 3.5 中定义的。因此,要运行包含任何这些方法的程序集,您需要安装 .NET Framework 3.5 或更高版本。
LINQ查询语法和查询表达式
借助内置 LINQ 扩展方法和 lambda 表达式,Visual Studio 允许我们在调用这些方法时在 C# 中编写类似 SQL 的语句。这些语句的语法称为 LINQ 查询语法,查询语法中的表达式称为查询表达式。
例如,我们可以更改此语句
var veges6 = products.Where(p => p.ProductName.Contains("vegetable"));
使用新的 LINQ 查询语法,更改为以下查询语句
var veges7 = from p in products
where p.ProductName.Contains("vegetable")
select p;
在上面的 C# 语句中,我们可以直接使用 SQL 关键字 `select`、`from` 和 `where` 来“查询”内存中的集合列表。除了内存中的集合列表,我们还可以使用相同的语法来操作 XML 文件、数据集和数据库中的数据。在下面的文章中,我们将看到如何使用 LINQ to SQL 和 LINQ to Entities 查询数据库。
结合匿名数据类型,我们可以通过以下语句塑造查询结果
var candyOrVeges = from p in products
where p.ProductName.Contains("candy")
|| p.ProductName.Contains("vegetable")
orderby p.UnitPrice descending, p.ProductID
select new { p.ProductName, p.UnitPrice };
正如你所见,查询语法是一种非常方便、声明式的速记方式,用于使用标准 LINQ 查询运算符表达查询。它提供了一种语法,可以提高代码中查询表达的可读性和清晰度,并且易于正确阅读和编写。
查询语法不仅易于读写,Visual Studio 实际上还为查询语法提供了完整的智能感知和编译时检查支持。例如,当输入 p 和后面的点时,智能感知列表会列出所有 `Product` 成员,如下图所示
如果语法中有拼写错误(例如此语句:`where p.productName.Contains("vegetable")`),编译器会准确地告诉你错误在哪里以及为什么错误。不会出现“无效 SQL 语句”等运行时错误。下图显示了语句中存在拼写错误时的错误消息
如您所见,您可以用查询语法编写 LINQ 语句,就像在 Query Analyzer 中使用数据库一样。然而,.NET 公共语言运行时 (CLR) 没有查询语法的概念。因此,在编译时,查询表达式会转换为 CLR 能够理解的内容:方法调用。在底层,编译器获取查询语法表达式并将其转换为显式方法调用代码,该代码利用 C# 3.0 中新的 LINQ 扩展方法和 lambda 表达式语言特性。
例如,`candyOrVeges` 查询表达式将被转换为此方法调用
products.Where(p => p.ProductName.Contains("candy")
|| p.ProductName.Contains("vegetable")).OrderByDescending(
p => p.UnitPrice).ThenBy(p=>p.ProductID).Select(p=>new { p.ProductName, p.UnitPrice })
您可以打印并比较使用查询语法和方法语法的C#代码结果,以确保它们是等效的。以下语句将使用查询语法打印出查询结果中产品的名称和单价。
foreach (var p in candyOrVeges)
{
Console.WriteLine("{0} {1}", p.ProductName, p.UnitPrice);
}
对使用方法语法的代码结果执行相同操作,您将获得如下打印输出
一般来说,推荐使用查询语法而不是方法语法,因为它通常更简单,更具可读性。但是,方法语法和查询语法之间没有语义上的区别。
内置LINQ运算符
正如我们在前面章节中看到的,方法语法和查询语法之间没有语义上的区别。此外,某些查询,例如检索符合指定条件的元素数量的查询,或检索源序列中具有最大值的元素的查询,只能表示为方法调用。这些方法有时被称为 .NET 标准查询运算符,包括 `Take`、`ToList`、`FirstOrDefault`、`Max` 和 `Min`。
除了那些只能用方法调用表达的方法外,所有可以在查询语法或方法语法中使用的扩展方法也被定义为标准查询运算符,例如 `select`、`where` 和 `from`。因此,.NET 标准查询运算符包含所有与 LINQ 相关的方法。
这些运算符的完整列表可在 Microsoft MSDN 库中找到 `System.Linq.Enumerable` 类。
要快速查看所有这些运算符,请在 Visual Studio 中打开 `program.cs` 文件,然后输入 `System.Linq.Enumerable`。然后,在 `Enumerable` 后面输入一个点。您将在智能感知菜单中看到完整的运算符列表。
此静态类中的方法为实现 `IEnumerable<(Of <(T>)>)>` 的数据源提供标准查询运算符的实现。标准查询运算符是通用方法,遵循 LINQ 模式,使您能够以任何基于 .NET 的编程语言表达数据的遍历、过滤和投影操作。
该类中的大多数方法被定义为扩展方法,用于扩展 `IEnumerable<(Of <(T>)>)>`。这意味着它们可以像实例方法一样在任何实现 `IEnumerable<(Of <(T>)>)>` 的对象上调用。
摘要
在本文中,我们学习了与 LINQ 相关的新功能,包括新的数据类型 `var`、对象和集合初始化器、扩展方法、lambda 表达式、LINQ 语法和查询表达式。现在我们已经掌握了 LINQ 所需的知识,我们已准备好尝试 LINQ to SQL 和 LINQ to Entities,这将在后续文章中讨论。
本文涵盖的要点包括
- 新的数据类型 `var` 在定义新变量时提供了额外的灵活性
- 自动属性功能可用于定义简单属性
- 可以使用对象初始化器和集合初始化器为新对象和集合变量赋值初始值
- 在编译时将为匿名类型创建实际类型
- 扩展方法可用于扩展现有 CLR 类型的公共契约,而无需子类化或重新编译原始类型
- Lambda 表达式只是以更简洁、函数式语法编写匿名方法的另一种方式
- 许多 LINQ 特定扩展方法已在 .NET Framework 3.5 中预定义
- 所有 .NET 标准 LINQ 查询运算符都在静态类 `System.Linq.Enumerable` 中定义
- LINQ 查询语法可用于在方法语法中创建表达式,但方法语法和查询语法之间没有语义差异
- 一些 LINQ 查询只能通过方法调用来表达
作者笔记
本文基于我的著作《WCF 4.0 多层服务开发与 LINQ to Entities》(ISBN 1849681147) 的第 6 章。本书是一本实践指南,教您如何使用 WCF 和 LINQ to Entities 在 Microsoft 平台上构建 SOA 应用程序。它从我之前的著作《WCF 多层服务开发与 LINQ》更新为 VS2010 版本。
通过本书,您可以通过完成实际示例并将其应用于实际任务,学习如何掌握 WCF 和 LINQ to Entities 概念。这是第一本也是唯一一本将 WCF 和 LINQ to Entities 结合在一个多层实际 WCF 服务中的书。它非常适合希望学习如何构建可扩展、强大、易于维护的 WCF 服务的初学者。本书内容丰富,包含示例代码、清晰的解释、有趣的示例和实用建议。对于 C++ 和 C# 开发人员来说,它是一本真正的实践指南。
您无需具备任何 WCF 或 LINQ to Entities 经验即可阅读本书。详细的说明和精确的屏幕截图将引导您完成探索 WCF 和 LINQ to Entities 新世界的整个过程。与其他 WCF 和 LINQ to Entities 书籍不同的是,本书侧重于如何操作,而不是为什么要以这种方式操作,因此您不会被海量的 WCF 和 LINQ to Entities 信息所淹没。读完本书后,您将为自己以最直接的方式使用 WCF 和 LINQ to Entities 而感到自豪。
您可以从亚马逊购买本书,或从出版商网站购买,网址为 https://www.packtpub.com/wcf-4-0-multi-tier-services-development-with-linq-to-entities/book。
更新
我的书《WCF Multi-layer Services Development with Entity Framework - Fourth Edition》(适用于 Visual Studio 2013 / Windows 7 和 Windows 8.1)的最新版本 WCF Multi-layer Services Development with Entity Framework - Fourth Edition 已出版。您可以直接从出版商的网站购买,网址为
或从亚马逊购买,地址如下
http://www.amazon.com/Multi-Layer-Services-Development-Entity-Framework/dp/1784391042