LINQ 入门 - 第 1 部分(共 3 部分)






4.92/5 (163投票s)
LINQ 简介
目录
引言
.NET 3.0 已经发布了,我们应该都已经了解了吧?天哪,感觉 .NET 2.0 没过多久才出现。对于那些不了解的人来说,.NET 3.0 其实包含了很多新东西,比如
- Windows Workflow Foundation (WWF): 管理对象生命周期/持久对象存储
- Windows Communication Foundation (WCF): 新的通信层
- Windows Presentation Foundation (WPF): 新的表示层 (XAML)
- Windows Cardspace: 提供基于标准的解决方案,用于处理和管理各种数字身份
所以,正如你所看到的,有很多东西需要学习。我正在学习 WPF/WCF,但我也对一个叫做 LINQ 的小宝石感兴趣,我相信它将成为 .NET 3.5 和 Visual Studio "Orcas" (它现在就是这么称呼的) 的一部分。LINQ 将为 C# 和 VB.NET 添加新功能。LINQ 有三种风味
- LINQ: 语言集成查询,用于内存对象 (LINQ to Objects)
- DINQ: 语言集成查询,用于数据库 (LINQ to ADO NET)
- XLINQ: 语言集成查询,用于 XML (LINQ to XML)
LINQ 非常酷,我最近一直在研究它,所以我写了一篇文章,分享我在 LINQ/DLINQ/XLINQ 领域的学习心得,希望对大家有所帮助。本文将专注于 LINQ,这是我计划的 3 篇文章系列的第一篇。
计划的文章系列内容如下
- 第一部分 (本文): 将全部介绍标准 LINQ,它用于查询内存数据对象,例如 List、数组等。
- 第二部分: 将介绍 DLINQ 的使用,它是用于数据库数据的 LINQ。
- 第三部分: 将介绍 XLINQ 的使用,它是用于 XML 数据的 LINQ。
LINQ 究竟是什么?
嗯,标准 LINQ 是 .NET 的一个新 addition (它主要增加了更多 DLL),它允许程序员像使用标准的 SQL 语法一样,以内联方式查询数据。
所以,他们以前可能在数据库中进行查询,或者使用一个 SQL 查询字符串,如下所示
SELECT * from Books WHERE QuatityInStock > 50 AND Price > 50.00
现在,我们将编写以下内容作为有效的 LINQ 查询 (假设我们有相关的内存数据结构来支持该查询)var result =
from b Books
where b.QuatityInStock > 50 AND Price > 50.00
select b;
看看它有多相似。它非常强大。所以,基本上 LINQ 允许我们做这些。正如人们所能想象的,DLINQ 做的事情也类似,但对象是数据库对象,而 XLINQ 对 XML 文档进行查询/创建。
LINQ 还引入了许多概念,这些概念实际上来源于其他函数式编程语言,如 Haskell、LISP。其中一些新概念包括
- Lambda 表达式 (它允许匿名函数 (在 .NET 中是方法) 在序列上调用,一个关于这个的好资源在这里:这里)
- 序列上的递归处理
- 惰性求值
希望随着我们继续学习,这些概念会变得更加熟悉。
必备组件
要运行本文提供的代码,您需要安装 May 2006 LINQ CTP,可以在此处找到。有一个新的 March 2007 CTP 可用,但完整的安装包大约为 4GB (因为它不仅仅是 LINQ,而是整个下一代 Visual Studio,代号为 "Orcas"),并且相当繁琐,而且很可能会再次更改,因此 May 2006 LINQ CTP 对于本文旨在演示的目的来说已经足够。
其他有趣阅读
有许多有趣的 LINQ 和函数式编程概念的来源。当然有 LINQ 网站,还有一些好的网络示例,以及 Code Project 上的其他文章。我将列出一些供好奇并想了解更多的人参考
- Lambda 表达式和表达式树:简介
- Lambda 和 Curry 的笔记。来自苏塞克斯大学 (实际上是我的大学)
- C# 3.0 语言背后的概念
- LINQ 项目
- 101 LINQ 示例
- 构建查询
- Wayward WebLog
本文不包含的内容
我已经告诉您在哪里下载 LINQ,并为您提供了其他 LINQ 资源和进一步阅读材料 (我强烈建议您去看看),所以现在您可能在想 "还有什么要讨论的?" 嗯,老实说,本文的内容很可能可以在上面列出的其他资源中很容易找到。但你永远不知道,这篇文章可能会带来新的视角,并帮助您以不同的方式理解 LINQ,因为每个人的写作风格都不同,学习风格也不同。有些人可能就是喜欢这篇文章。说实话,我写文章也很享受,所以我将继续写下去,希望有人会喜欢本文的内容。
然而,我确实想让大家知道 (以免有人认为我是在推销新知识),本文中的所有信息都不是新颖的,也不是原创的,都可以很容易地通过网络或查阅 LINQ 文档找到。但有时,让别人为您完成学习过程,并从中学习,也是一件好事。就当是我的 LINQ 学习之旅,在这里与您分享。
尽管说实话,真正让我兴奋的并不是标准 LINQ,而是 DLINQ/XLINQ,对于这两者,免费提供的信息相对较少。所以这确实需要查阅文档。但别担心,我将在接下来的两篇文章中为您进行这项工作。所以请继续关注这些未来的文章。在没有对标准 LINQ 做一些介绍之前,直接写那两篇是没有意义的。
仍然感兴趣?
If (Yes==UserResponse)
{
SELECT RestOfArticleContent
}
关于附带的演示项目的一点说明
在深入研究 LINQ 的细节之前,我想先介绍一下提供的演示应用程序。它看起来像下图所示。
正如您所看到的,它包括一个左侧面板和一个右侧区域。在左侧,用户可以查看每个源 List 的 PropertyGrid 和 Numeric Up / Down 控件。
用户可以使用 Numeric Up / Down 控件检查单个查询源 List
的数据元素,而 PropertyGrid 将始终显示用户请求的当前列表项。查询源 List
可能不总是相同的 List
,这取决于正在执行的查询类型。然而,PropertyGrid 始终允许用户以刚才描述的方式检查当前查询源 List
。
用于大多数查询的主要数据查询源将是简单的基于 List
对象的,这些对象包含一些非常简单的类对象。让我们看一个示例数据 List
对象
_itemList = new List<Item> {
{ ItemID = 1, ItemName = "Enclopedia",
Category="Knowledge", UnitPrice = 55.99M, UnitsInStock = 39 },
{ ItemID = 2, ItemName = "Trainers",
Category="Sports", UnitPrice = 75.00M, UnitsInStock = 17 },
{ ItemID = 3, ItemName = "Box of CDs",
Category="Storage", UnitPrice = 4.99M, UnitsInStock = 13 },
{ ItemID = 4, ItemName = "Tomatoe ketchup",
Category="Food", UnitPrice = 0.56M, UnitsInStock = 53 },
{ ItemID = 5, ItemName = "IPod",
Category="Entertainment", UnitPrice = 220.99M, UnitsInStock
= 0 },
{ ItemID = 6, ItemName = "Rammstein CD",
Category="Entertainment", UnitPrice = 7.99M, UnitsInStock =
120 },
{ ItemID = 7, ItemName = "War of the worlds DVD",
Category="Entertainment", UnitPrice = 6.99M, UnitsInStock =
15 },
{ ItemID = 8, ItemName = "Cranberry Sauce",
Category="Food", UnitPrice = 0.89M, UnitsInStock = 6 },
{ ItemID = 9, ItemName = "Rice steamer",
Category="Food", UnitPrice = 13.00M, UnitsInStock = 29 },
{ ItemID = 10, ItemName = "Bunch of grapes",
Category="Food", UnitPrice = 1.19M, UnitsInStock = 4 }};
_orderList = new List<Order> {
{ OrderID = 1, OrderName = "John Smith", OrderDate =
DateTime.Now },
{ OrderID = 2, OrderName = "Professor X", OrderDate =
DateTime.Now },
{ OrderID = 3, OrderName = "Naomi Campbell", OrderDate =
DateTime.Now },
{ OrderID = 4, OrderName = "The Hulk", OrderDate =
DateTime.Now },
{ OrderID = 5, OrderName = "Malcolm X", OrderDate =
DateTime.Now }};
因此,可以看出第一个 List
简单地包含 10 个 Item
对象,第二个 List
简单地包含 10 个 Order
对象。但是这些 Item
和 Order
对象看起来是什么样的呢?正如我之前所说的,它们是非常简单的对象,非常“笨拙”,仅仅是为了展示 LINQ 所能实现的才华。
using System;
using System.Collections.Generic;
using System.Text;
namespace LinqApp
{
public class Item
{
#region Instance fields
private int itemID;
private string itemName;
private string category;
private decimal unitPrice;
private int unitsInStock;
#endregion
#region Ctor
/// <summary>
/// ctor
/// </summary>
public Item()
{
}
#endregion
#region Public Properties
public int ItemID
{
get { return itemID;}
set { itemID = value; }
}
public string ItemName
{
get { return itemName;}
set { itemName = value; }
}
public string Category
{
get { return category;}
set { category = value; }
}
public decimal UnitPrice
{
get { return unitPrice;}
set { unitPrice = value; }
}
public int UnitsInStock
{
get { return unitsInStock;}
set { unitsInStock = value; }
}
public string ToString()
{
return "ItemID :" + ItemID + "\r\n" +
"ItemName :" + ItemName + "\r\n" +
"Category :" + Category + "\r\n" +
"UnitPrice :" + UnitPrice + "\r\n" +
"UnitsInStock :" + UnitsInStock;
}
#endregion
}
}
using System;
using System.Collections.Generic;
using System.Text;
namespace LinqApp
{
public class Order
{
#region Instance fields
private int orderID;
private string orderName;
private DateTime orderDate;
#endregion
#region Ctor
/// <summary>
/// ctor
/// </summary>
public Order()
{
}
#endregion
#region Public Properties
public int OrderID
{
get { return orderID;}
set { orderID = value; }
}
public string OrderName
{
get { return orderName;}
set { orderName = value; }
}
public DateTime OrderDate
{
get { return orderDate;}
set { orderDate = value; }
}
public string ToString()
{
return "OrderID :" + OrderID + "\r\n" +
"OrderName :" + OrderName + "\r\n" +
"OrderDate :" + OrderDate;
}
#endregion
}
}
演示应用程序的右侧区域显示了当前的查询 (实际 LINQ 语法) 和获得的结果。
演示应用程序中的所有源数据基本上都是这样做的,可能有一些例外,例如使用简单的值数组而不是 List
对象,但我会在我们遇到它们时提到。
将演示应用程序视为一个迷你 LINQ 游乐场。
那么,关于演示应用程序,我想您目前只需要知道这些就够了,我们继续吧?
好的,我们开始吧
正如我之前所说,标准 LINQ (不是 DLINQ / XLINQ) 操作内存中的数据结构,如数组、集合等。LINQ 实际上是通过称为“标准查询运算符”的方法来实现的。
新的 .NET 3.0 System.Query.Sequence
静态类声明了一组方法,这些方法公开了这些标准查询运算符。
大多数标准查询运算符是扩展方法,它们扩展了 IEnumerable<T>
。
我认为解决这个问题的最佳方法是介绍 LINQ 标准查询运算符,并为每个运算符提供正式定义和示例。
LINQ 规范详细介绍了以下运算符
- 筛选运算符
- 投影运算符
- 分区运算符
- 连接运算符
- 连接运算符
- 排序运算符
- 分组运算符
- 集合运算符
- 转换运算符
- 相等运算符
- 元素运算符
- 生成运算符
- 量词
- 聚合运算符
其中,标准查询运算符作用于序列。任何实现 IEnumerable<T>
接口的对象,对于某个类型 T,都被视为该类型的序列。
所以,您可以看到这是一个相当大的工程。我将尝试为每个运算符提供一个正式定义和一个示例。我将把进一步的阅读 (因为我只展示一个示例,每个运算符都有多种可能性) 作为读者练习。
运算符说明
是时候举例了。
筛选 (WHERE) 运算符
筛选运算符根据谓词筛选序列。
public static IEnumerable<T> Where<T>(
this IEnumerable<T> source,
Func<T, bool> predicate);
public static IEnumerable<T> Where<T>(
this IEnumerable<T> source,
Func<T, int, bool> predicate);
谓词
你说,什么是谓词?
维基百科说
"在形式语义学中,谓词是语义类型为集合的表达式。一种等价的表述是,它们被认为是集合的指示函数,即从实体到真值的函数。
在谓词逻辑中,谓词可以充当属性或实体之间的关系。"
而 LINQ 标准查询运算符文档说
"下面的示例声明了一个局部变量 predicate,其类型为委托,该委托接受一个 Customer 并返回 bool。局部变量被赋值为一个匿名方法,如果给定的客户位于伦敦,则返回 true。然后使用 predicate 引用的委托来查找伦敦的所有客户。"
Func<Customer, bool> predicate = c => c.City == "London";
IEnumerable<Customer> customersInLondon = customers.Where(predicate);
所以,让我们考虑这个简单的例子。我们将得到一个表达式,它看起来像
IEnumerable<Customer> customersInLondon = customers.Where(c => c.City
== "London");
或者可以用一种更常规的方式来写 (那些更熟悉 SQL 的人可能会喜欢)
IEnumerable<Customer> customersInLondon =
from c in customers
where c.City == "London"
select c;
我们甚至可以比这更“懒惰”,而不是写
IEnumerable<Customer> customersInLondon =
from c in customers
where c.City == "London"
select c;
我们可以写
var customersInLondon =
from c in customers
where c.City == "London"
select c;
LINQ 就是这样使用谓词的。基本上,思考谓词最简单的方式就是将它们视为会评估为 True 或 False 的过滤器,因此它们只会过滤应用 Expression
的 IEnumerable<T>
数据源中的那些匹配过滤器 (谓词) 的元素。
但我们有点跑题了。我们曾经需要这样做一次,以便解释谓词。但现在你们都掌握了这一点,我们将重新审视第一个标准查询运算符。
筛选 (WHERE) 运算符回顾
回忆一下,筛选 (Where) 查询运算符的定义是
public static IEnumerable<T> Where<T>(
this IEnumerable<T> source,
Func<T, bool> predicate);
public static IEnumerable<T> Where<T>(
this IEnumerable<T> source,
Func<T, int, bool> predicate);
筛选查询运算符可以采用以上两种形式之一,其中谓词函数的第一个参数代表要测试的元素。第二个参数 (如果存在) 代表源序列中元素的零基索引。
那么,让我们看一个实际的例子 (使用附带的演示项目和 _itemList
List
本地数据源)
var iSmall =
from it in _itemList
where it.UnitPrice < 50.00M
select it;
关于“Var”的一点说明
这里有趣的一点是,在上面的示例中使用了 var
。这让人想起过去的时代 - VB、Flash、JavaScript - 基本上任何非强类型语言。而那些日子很糟糕。如今,我们期望并使用强类型对象。甚至更好的是,现在我们还有泛型,它们为我们编写的软件带来了更多的类型控制。然而,这里有 LINQ 代码,它是全新的东西,很可能将成为 .NET 3.0 的一部分。这是好事吗?
考虑以下声明
"查询变量的类型也不必声明,因为 var 关键字使用时,类型推断会自动推断类型。"
C# 3.0 语言背后的概念,Tomas Petricek。
我们怎么看待这个?嗯,这肯定比 VB 以前的做法要好,VB 是在运行时确定类型。LINQ 的做法是在编译时确定类型。所以,明智地使用 var
类型实际上可以帮助开发者并减少编码时间。当然,如果您真的想坚持硬核类型,那么您需要做一些像下面这样的事情
示例 1
IEnumerable<Item> iSmall =
from it in _itemList
where it.UnitPrice < 50.00M
select it;
而不是
示例 2
var iSmall =
from it in _itemList
where it.UnitPrice < 50.00M
select it;
你能看出区别吗?
在第一种情况下,我们实际上是在选择结果类型,碰巧是 Type Item
类型,所以我们必须将查询结果声明为 IEnumerable<Item>
,因为这与查询结果匹配。这就是如何强类型化查询结果。然而,如果查询被更改为不返回 Item Type
,比如说,一个 string Type
,那么我们就需要将查询结果类型从 IEnumerable<Item>
更改为 IEnumerable<string>
,我们就必须记住这一点。此外,如果我们有一个复杂的嵌套、连接、聚合 (SUM、COUNT) 类型运算符作为查询的一部分,那么我们需要声明的返回类型可能会相当复杂。
您认为这个查询结果类型会是什么?
from c in customers
join o in orders on c.CustomerID equals o.CustomerID into co
from o in co.DefaultIfEmpty(emptyOrder)
select new { c.Name, o.OrderDate, o.Total };
或者这个呢?
from c in customers
select new
{
c.CompanyName, YearGroups =
from o in c.Orders
group o by o.OrderDate.Year into yg
select new
{
Year = yg.Key, MonthGroups =
from o in yg
group o by o.OrderDate.Month into mg
select new
{
Month = mg.Key, Orders = mg
}
}
};
看,这变得很棘手。我们都知道类型是好的,是我们的朋友。但有时它也会相当复杂。
在上面的第二个示例中,我们只是使用 var
而不是强类型化查询结果。这可行,并且正确类型的推断,即使对于最复杂的查询结果也是如此。此外,如果我们更改查询,我们不必更改 var
,因为它会自动推断所需的新类型。任务完成。
这是个人偏好,但 var
可以节省时间和挫败感。明智地使用它,一切都会很好。
投影 (SELECT) 运算符
投影运算符对序列执行投影。
public static IEnumerable<S> Select<T, S>(
this IEnumerable<T> source,
Func<T, S> selector);
public static IEnumerable<S> Select<T, S>(
this IEnumerable<T> source,
Func<T, int, S> selector);
投影查询运算符可以采用以上两种形式之一,其中选择器函数的第一个参数代表要处理的元素。第二个参数 (如果存在) 代表源序列中元素的零基索引。
那么,让我们看一个实际的例子 (使用附带的演示项目和 _itemList
List
本地数据源)
var iNames = from i in _itemList select i.ItemName;
或许再举一个例子
var namesAndPrices =
_itemList.Where(i => i.UnitPrice >= 10).Select(i => new { i.ItemName,
i.UnitPrice }).ToList();
这是一个有趣的声明。第一部分是谓词 i => i.UnitPrice >= 10
,所以只有 UnitPrice
大于 10 的项目才被选中。然后,在被选中的项目之中,我们生成一个新的列表 (使用 ToList()
方法),该列表只包含 ItemName
和 UnitPrice
。很巧妙,是吧?
还有 SelectMany,我没有在这里包含。但安装 LINQ 后,您可以自己尝试。
分区 (Take / Skip) 运算符
分区运算符由四部分组成
Take
public static IEnumerable<T> Take<T>(this IEnumerable<T> source,int count);
那么,让我们看一个实际的例子 (使用附带的演示项目和 _itemList
List
本地数据源)
var MostExpensive2 = _itemList.OrderByDescending(i => i.UnitPrice).Take(2);
此示例获取两个最昂贵的 Item。
Skip
public static IEnumerable<T> Skip<T>(
this IEnumerable<T> source,int count);
那么,让我们看一个实际的例子 (使用附带的演示项目和 _itemList
List
本地数据源)
var AllButMostExpensive2 =
_itemList.OrderByDescending(i => i.UnitPrice).Skip(2);
此示例获取除最昂贵的两个 Item 之外的所有 Item。
TakeWhile
TakeWhile
运算符在测试为 true 时从序列中提取元素,然后跳过序列的其余部分。我将把这个留给读者练习。
SkipWhile
SkipWhile
运算符在测试为 true 时跳过序列中的元素,然后提取序列的其余部分。我将把这个留给读者练习。
Join 运算符
Join
运算符由两部分组成
Join
public static IEnumerable<V> Join<T, U, K, V>(
this IEnumerable<T> outer,
IEnumerable<U> inner,
Func<T, K> outerKeySelector,
Func<U, K>> innerKeySelector,
Func<T, U, V> resultSelector);
它看起来相当棘手,但将其分解一下,它实际上只是说获取一个外部数据源,获取一个内部数据源,获取一个外部键,获取一个内部键,然后获取结果集。
那么,让我们看一个实际的例子 (使用附带的演示项目和 _itemList
List
和 _orderList
本地数据源)
var itemOrders =
from i in _itemList
join o in _orderList on i.ItemID equals o.OrderID
select new { i.ItemName, o.OrderName };
此示例获取所有 Order 对象,这些对象的 OrderID 值与当前 Item.ItemID 的值相同。
JoinGroup
GroupJoin
运算符根据从元素中提取的匹配键对两个序列执行分组连接。我将把这个留给读者练习。
连接运算符
Concat 运算符连接两个序列。
public static IEnumerable<T> Concat<T>(
this IEnumerable<T> first,
IEnumerable<T> second);
那么,让我们看一个实际的例子 (使用附带的演示项目和 _itemList
List
本地数据源)
var items = ( from itEnt in _itemList
where itEnt.Category.Equals("Entertainment")
select itEnt.ItemName
).Concat
(
from it2 in _itemList
where it2.Category.Equals("Food")
select it2.ItemName
).Distinct();
此示例获取所有 Category 为“Entertainment”的 Item 对象,并将它们与 Category 为“Food”的所有 Item 对象的结果连接起来,并通过使用 Distinct()
方法确保没有重复项。
Order (OrderBy / ThenBy) 运算符
OrderBy
/ ThenBy
系列运算符根据一个或多个键对序列进行排序。
public static OrderedSequence<T> OrderBy<T, K>(
this IEnumerable<T> source,
Func<T, K> keySelector);
public static OrderedSequence<T> OrderBy<T, K>(
this IEnumerable<T> source,
Func<T, K> keySelector, IComparer<K> comparer);
public static OrderedSequence<T> OrderByDescending<T, K>(
this IEnumerable<T> source,
Func<T, K> keySelector);
public static OrderedSequence<T> OrderByDescending<T, K>(
this IEnumerable<T> source,
Func<T, K> keySelector,
IComparer<K> comparer);
public static OrderedSequence<T> ThenBy<T, K>(
this OrderedSequence<T> source,
Func<T, K> keySelector);
public static OrderedSequence<T> ThenBy<T, K>(
this OrderedSequence<T> source,
Func<T, K> keySelector,
IComparer<K> comparer);
public static OrderedSequence<T> ThenByDescending<T, K>(
this OrderedSequence<T> source,
Func<T, K> keySelector);
public static OrderedSequence<T> ThenByDescending<T, K>(
this OrderedSequence<T> source,
Func<T, K> keySelector,
IComparer<K> comparer);
那么,让我们看一个实际的例子 (使用附带的演示项目和 _itemList
List
本地数据源)
var orderItems =
_itemList.OrderBy(i => i.Category).ThenByDescending(i => i.UnitPrice);
此示例获取所有 Item 对象,并首先按 Category 排序,然后按 UnitPrice 排序。
Group (GroupBy) 运算符
GroupBy
运算符对序列的元素进行分组。
public static IEnumerable<IGrouping<K, T>> GroupBy<T, K>(
this IEnumerable<T> source,
Func<T, K> keySelector);
public static IEnumerable<IGrouping<K, T>> GroupBy<T, K>(
this IEnumerable<T> source,
Func<T, K> keySelector,
IEqualityComparer<K> comparer);
public static IEnumerable<IGrouping<K, E>> GroupBy<T, K, E>(
this IEnumerable<T> source,
Func<T, K> keySelector,
Func<T, E> elementSelector);
public static IEnumerable<IGrouping<K, E>> GroupBy<T, K, E>(
this IEnumerable<T> source,
Func<T, K> keySelector,
Func<T, E> elementSelector,
IEqualityComparer<K> comparer);
public interface IGrouping<K, T> : IEnumerable<T>
{
K Key { get; }
}
那么,让我们看一个实际的例子 (使用附带的演示项目和 _itemList
List
本地数据源)
var itemNamesByCategory =
from i in _itemList
group i by i.Category into g
select new { Category = g.Key, Items = g };
注意: 此示例与 LINQ CTP 提供的示例大不相同,我无法让那些示例工作,因为我认为自 Microsoft 编写 LINQ 文档以来,语法可能已发生变化 (例如,GroupBy 在此特定查询的描述方式下似乎不被支持)。上面的示例确实有效。
此示例获取所有 Item
对象,然后按 Category 对它们进行分组。然后,它将结果选择到一个新的 List 中 (即时创建),其中 Category 是上一步分组结果的键,而 Items 被设置为上一步中匹配当前分组的项目的值。有点令人困惑,但让我们看看结果,这可能会有所帮助。
Category : Knowledge Enclopedia Category : Sports Trainers Category : Storage Box of CDs Category : Food Tomatoe ketchup Cranberry Sauce Rice steamer Bunch of grapes Category : Entertainment IPod Rammstein CD War of the worlds DVD
可以看到,我们拥有来自初始 List
的所有 Item
对象,只是现在它们已经被分组了。我承认它不像标准的 SQL 语法那么好,但它确实完成了工作。
Set (Distinct / Union / Intersect / Except) 运算符
Set
运算符由四部分组成
Distinct
Distinct
运算符从序列中消除重复元素。
public static IEnumerable<T> Distinct<T>(
this IEnumerable<T> source);
那么,让我们看一个实际的例子 (使用附带的演示项目和 _itemList
List
本地数据源)
var itemCategory = (from i in _itemList select i.Category).Distinct()
此示例获取 Items List
的所有 Category 的唯一列表。
Union
Union
运算符产生两个序列的集合并集。
public static IEnumerable<T> Union<T>(
this IEnumerable<T> first,
IEnumerable<T> second);
那么,让我们看一个实际的例子 (使用附带的演示项目和 _itemList
和 _orderList
本地数据源)
var un = (from i in _itemList select i.ItemName).Distinct()
.Union((from o in _orderList select o.OrderName).Distinct());
此示例获取所有 ItemName
的唯一 List
,然后将此结果与所有 OrderName
的唯一 List
进行联合。
Intersect
Intersect
运算符产生两个序列的集合交集。
public static IEnumerable<T> Intersect<T>(
this IEnumerable<T> first,
IEnumerable<T> second);
那么,让我们看一个实际的例子 (使用附带的演示项目和 _itemList
和 _orderList
本地数据源)
var inter = (from i in _itemList select i.ItemID).Distinct()
.Intersect((from o in _orderList select o.OrderID).Distinct());
此示例获取所有 Item
的 ItemID
的唯一 List
,然后将此结果与所有 Order
的 OrderID
的唯一 List
进行相交。结果是一个 int 的 List
,它同时存在于 Item
和 Order List
中。
Except
Except
运算符产生两个序列之间的集合差。
public static IEnumerable<T> Except<T>(
this IEnumerable<T> first,
IEnumerable<T> second);
那么,让我们看一个实际的例子 (使用附带的演示项目和 _itemList
和 _orderList
本地数据源)
var inter = (from i in _itemList select i.ItemID).Distinct()
.Intersect((from o in _orderList select o.OrderID).Distinct());
此示例获取所有 Item
的 ItemID
的唯一 List
,然后将此结果与所有 Order
的 OrderID
的唯一 List
进行相交。结果是一个 int 的 List
,它同时存在于 Item
和 Order List
中。
转换 (ToSequence / ToArray / ToList / ... ) 运算符
Set
运算符由七部分组成
ToSequence
ToSequence
运算符将其参数作为 IEnumerable<T>
类型返回。
public static IEnumerable<T> ToSequence<T>(
this IEnumerable<T> source);
那么,让我们看一个实际的例子 (使用附带的演示项目和 _itemList List
本地数据源)
var query = _itemList.ToSequence().Where(i => i.Category.Equals(
"Entertainment"));
此示例将 Item
的 List
转换为 Sequence
,然后获取所有 Category 为“Entertainment”的元素。
ToArray
ToArray
运算符从序列创建数组。
public static T[] ToArray<T>(
this IEnumerable<T> source);
那么,让我们看一个实际的例子 (使用附带的演示项目和 _itemList
List
本地数据源)
var query = _itemList.ToArray().Where(i => i.Category.Equals("Food"));
此示例将 Item
的 List
转换为数组,然后获取所有 Category 为“Food”的元素。
ToList
ToList
运算符从序列创建 List<T>
。
public static List<T> ToList<T>(
this IEnumerable<T> source);
那么,让我们看一个实际的例子 (使用附带的演示项目和 _itemList
List
本地数据源)
var query = _itemList.ToList().Where(i => i.ItemID > 5).Reverse();
此示例将源 List
的 Item
转换为 List
,然后获取所有 ItemID 大于 5 的元素并反转它,以便数字较大的元素在开头。
ToDictionary
ToDictionary
运算符从序列创建 Dictionary<K,E>
。
public static Dictionary<K, T> ToDictionary<T, K>(
this IEnumerable<T> source,
Func<T, K> keySelector);
public static Dictionary<K, T> ToDictionary<T, K>(
this IEnumerable<T> source,
Func<T, K> keySelector,
IEqualityComparer<K> comparer);
public static Dictionary<K, E> ToDictionary<T, K, E>(
this IEnumerable<T> source,
Func<T, K> keySelector,
Func<T, E> elementSelector);
public static Dictionary<K, E> ToDictionary<T, K, E>(
this IEnumerable<T> source,
Func<T, K> keySelector,
Func<T, E> elementSelector,
IEqualityComparer<K> comparer);
那么,让我们看一个实际的例子 (使用演示项目,其中使用了简单的二维数组)
var scoreRecords = new [] { new {Name = "Alice", Score = 50},
new {Name = "Bob" , Score = 40},
new {Name = "Cathy", Score = 45}
};
var scoreRecordsDict = scoreRecords.ToDictionary(sr => sr.Name);
此示例创建一个新的二维数组,并将其转换为键/值 Dictionary
对对象。
ToLookup
ToLookup
运算符从序列创建 Lookup<K, T>
。我将把这个留给读者探索。
OfType
OfType
运算符根据类型筛选序列中的元素。
public static IEnumerable<T> OfType<T>(
this IEnumerable source);
那么,让我们看一个实际的例子 (使用演示项目,其中使用了简单的数组)
object[] numbers = { null, 1.0, "two", 3, 4.0f, 5, "six", 7.0 };
var doubles = numbers.OfType<double>();
此示例创建一个包含各种对象的数组,然后使用 OfType
来仅获取类型为 Double
的对象。
Cast
Cast
运算符将序列中的元素强制转换为给定的类型。我将把这个留给读者探索。
相等运算符
EqualAll 运算符检查两个序列是否相等。
public static bool EqualAll<T>(
this IEnumerable<T> first,
IEnumerable<T> second);
那么,让我们看一个实际的例子 (使用附带的演示项目和 _itemList
和 _orderList List
本地数据源)
这是一个不等于的例子
var eq = (from i in _itemList select i.ItemID).Distinct().EqualAll((
from o in _orderList select o.OrderID).Distinct());
此示例获取所有 Item
对象的 ItemID 和所有 Order
对象的 OrderID,并检查整个序列是否相等。它们不相等,因为 Item List
包含的元素比 Order List
多,因此某些 ItemID 出现在 Order List
中。
这是一个等于的例子
int[] scoreRecords1 = new [] { 10,20 };
int[] scoreRecords2 = new [] { 10,20 };
var eq2 = scoreRecords1.EqualAll(scoreRecords2);
此示例创建两个具有相同元素的数组,因此当使用 EqualAll
比较它们时,它们被认为是相等的。
元素 (First / FirstOrDefault / ... ) 运算符
Set 运算符由九部分组成
First
First
运算符返回序列的第一个元素。
public static T First<T>(
this IEnumerable<T> source);
public static T First<T>(
this IEnumerable<T> source,
Func<T, bool> predicate);
First
运算符枚举源序列并返回谓词函数返回 true 的第一个元素。如果未指定谓词函数,则 First 运算符直接返回序列的第一个元素。
那么,让我们看一个实际的例子 (使用附带的演示项目和 _itemList
List
本地数据源)
string itemName = "War of the worlds DVD";
Item itm = _itemList.First(i => i.ItemName == itemName);
此示例采用 List
of Item
中匹配谓词 i.ItemName == itemName
的第一个元素,其中 itemName = "War of the worlds DVD"
。
FirstOrDefault
FirstOrDefault
运算符返回序列的第一个元素,如果找不到元素,则返回默认值。
public static T FirstOrDefault<T>(
this IEnumerable<T> source);
public static T FirstOrDefault<T>(
this IEnumerable<T> source,
Func<T, bool> predicate);
FirstOrDefault
运算符枚举源序列并返回谓词函数返回 true 的第一个元素。如果未指定谓词函数,则 FirstOrDefault
运算符直接返回序列的第一个元素。
那么,让我们看一个实际的例子 (使用附带的演示项目和 _itemList
List
本地数据源)
string itemName = "A Non existence Element";
Item itm = _itemList.FirstOrDefault(i => i.ItemName == itemName);
此示例采用 List
of Item
中匹配谓词 i.ItemName == itemName
的第一个或默认元素,其中 itemName = "A Non existence Element"
。在这种情况下,不匹配,因此返回 null
。
Last
Last
运算符返回序列的最后一个元素。
public static T Last<T>(
this IEnumerable<T> source);
public static T Last<T>(
this IEnumerable<T> source,
Func<T, bool> predicate);
Last
运算符枚举源序列并返回谓词函数返回 true 的最后一个元素。如果未指定谓词函数,则 Last
运算符直接返回序列的最后一个元素。
这与 First 的工作方式相同,我将把它留给读者练习。
LastOrDefault
LastOrDefault
运算符返回序列的最后一个元素,如果找不到元素,则返回默认值。
public static T LastOrDefault<T>(
this IEnumerable<T> source);
public static T LastOrDefault<T>(
this IEnumerable<T> source,
Func<T, bool> predicate);
LastOrDefault
运算符枚举源序列并返回谓词函数返回 true 的最后一个元素。如果未指定谓词函数,则 LastOrDefault
运算符直接返回序列的最后一个元素。
这与 FirstOrDefault
的工作方式相同,我将把它留给读者练习。
Single
Single
运算符返回序列的单个元素。
public static T Single<T>(
this IEnumerable<T> source);
public static T Single<T>(
this IEnumerable<T> source,
Func<T, bool> predicate);
Single
运算符枚举源序列并返回谓词函数返回 true 的单个元素。如果未指定谓词函数,则 Single
运算符直接返回序列的单个元素。
这与 First 的工作方式相同,我将把它留给读者练习。
SingleOrDefault
SingleOrDefault
运算符返回序列的单个元素,如果找不到元素,则返回默认值。
public static T SingleOrDefault<T>(
this IEnumerable<T> source);
public static T SingleOrDefault<T>(
this IEnumerable<T> source,
Func<T, bool> predicate);
SingleOrDefault
运算符枚举源序列并返回谓词函数返回 true 的单个元素。如果未指定谓词函数,则 SingleOrDefault
运算符直接返回序列的单个元素。
这与 FirstOrDefault
的工作方式相同,我将把它留给读者练习。
ElementAt
ElementAt
运算符返回序列中给定索引处的元素。
public static T First<T>(
this IEnumerable<T> source);
public static T First<T>(
this IEnumerable<T> source,
Func<T, bool> predicate);
ElementAt
运算符首先检查源序列是否实现 IList<T>。如果是,则使用源序列的 IList<T> 实现来获取给定索引处的元素。否则,源序列将被枚举,直到跳过索引个元素,然后返回在该位置找到的元素。
那么,让我们看一个实际的例子 (使用附带的演示项目和 _itemList
List
本地数据源)
Item thirdMostExpensive =
_itemList.OrderByDescending(i => i.UnitPrice).ElementAt(2);
此示例按 UnitPrice 对源 List
of Item
进行排序,然后获取第三个 Item
。
ElementAtOrDefault
ElementAtOrDefault
运算符返回序列中给定索引处的元素,如果索引超出范围,则返回默认值。
public static T ElementAtOrDefault<T>(
this IEnumerable<T> source,
int index);
ElementAtOrDefault
运算符首先检查源序列是否实现 IList<T>。如果是,则使用源序列的 IList<T> 实现来获取给定索引处的元素。否则,源序列将被枚举,直到跳过索引个元素,然后返回在该位置找到的元素。
那么,让我们看一个实际的例子 (使用附带的演示项目和 _itemList
List
本地数据源)
Item itm = _itemList.ElementAtOrDefault(15);
此示例简单地尝试获取 List
of Item
中不存在的元素,因此返回 null
。
DefaultIfEmpty
DefaultIfEmpty
运算符为空序列提供一个默认元素。
public static IEnumerable<T> DefaultIfEmpty<T>(
this IEnumerable<T> source);
public static IEnumerable<T> DefaultIfEmpty<T>(
this IEnumerable<T> source,
T defaultValue);
DefaultIfEmpty
运算符分配并返回一个可枚举对象,该对象捕获传递给运算符的参数。
这与 FirstOrDefault 的工作方式相同,我将把它留给读者练习。
生成 (Range / Repeat / Empty ) 运算符
Set 运算符由三部分组成
Range
Range 运算符生成一个整数序列。
public static IEnumerable<int> Range(
int start,
int count);
Range 运算符分配并返回一个可枚举对象,该对象捕获参数。
那么,让我们看一个实际的例子 (使用附带的演示项目)
int[] squares = Sequence.Range(1, 10).Select(x => x * x).ToArray();
此示例通过使用静态 Sequence.Range
方法创建由平方数 (1-10) 组成的数组。
Repeat
Repeat 运算符通过重复一个值给定的次数来生成序列。
public static IEnumerable<T> Repeat<T>(
T element,
int count);
Repeat 运算符分配并返回一个可枚举对象,该对象捕获参数。
那么,让我们看一个实际的例子 (使用附带的演示项目)
int[] numbers = Sequence.Repeat(5, 5).ToArray();
此示例使用 Sequence.Repeat
方法创建由 5 个重复的 5 组成的数组。
Empty
Repeat 运算符通过重复一个值给定的次数来生成序列。
public static IEnumerable<T> Empty<T>();
Empty 运算符缓存给定类型的单个空序列。当枚举 Empty 返回的对象时,它不产生任何内容。
我不确定您为什么要这样做,所以我将把它留给读者练习。
量词 (Any / All / Contains ) 运算符
Set 运算符由三部分组成
任意
Any
运算符检查序列中的任何元素是否满足条件。
public static bool Any<T>(
this IEnumerable<T> source);
public static bool Any<T>(
this IEnumerable<T> source,
Func<T, bool> predicate);
Any
运算符枚举源序列,如果任何元素满足谓词给出的测试,则返回 true。如果未指定谓词函数,则只要源序列包含任何元素,Any
运算符就简单地返回 true。
只要结果已知,源序列的枚举就会终止。
那么,让我们看一个实际的例子 (使用附带的演示项目)
bool b = _itemList.Any(i => i.UnitPrice >= 400);
此示例仅在存在 UnitPrice > 400
的 Item
时返回 bool
。
全部
All
运算符检查序列中的所有元素是否满足条件。
public static bool All<T>(
this IEnumerable<T> source,
Func<T, bool> predicate);
All
运算符枚举源序列,如果没有任何元素未能通过谓词给出的测试,则返回 true。
只要结果已知,源序列的枚举就会终止。
那么,让我们看一个实际的例子 (使用附带的演示项目和 _itemList
List
本地数据源)
var itemNamesByCategory =
from i in _itemList
group i by i.Category into g
where g.All(i => i.UnitsInStock > 0)
select new { Category = g.Key, Items = g };
此示例使用 group
运算符,然后使用 All
运算符来获取满足谓词 i.UnitsInStock > 0
的 Items。
Contains
Contains
运算符检查序列是否包含给定的元素。
public static bool Contains<T>(
this IEnumerable<T> source,
T value);
Contains
运算符首先检查源序列是否实现 ICollection<T>。如果是,则调用序列的 ICollection<T> 实现中的 Contains 方法来获取结果。否则,将枚举源序列以确定它是否包含具有给定值的元素。如果找到匹配的元素,则在此处终止源序列的枚举。元素和给定值使用默认相等比较器 EqualityComparer<K>.Default 进行比较。
那么,让我们看一个实际的例子 (使用附带的演示项目和 _itemList
List
本地数据源)
bool b = _itemList.Contains(_itemList[0]);
此示例简单地返回一个 bool
,表示源 List
of Item
是否包含 _itemList[0]
,而它确实包含。
聚合 (Count / LongCount / Sum / ... ) 运算符
Set
运算符由七部分组成
Count
Count
运算符计算序列中的元素数量。
public static int Count<T>(
this IEnumerable<T> source);
public static int Count<T>(
this IEnumerable<T> source,
Func<T, bool> predicate);
不带谓词的 Count
运算符首先检查源序列是否实现 ICollectionICollection<t />
实现来获取元素计数。否则,将枚举源序列来计算元素数量。带谓词的 Count
运算符枚举源序列并计算满足谓词函数返回 true 的元素的数量。
那么,让我们看一个实际的例子 (使用附带的演示项目和 _itemList
List
本地数据源)
var foodCat = (from i in _itemList
where i.Category.Equals("Food")
select i).Count();
此示例简单地返回 Category.Equals("Food") 的所有 Item
的计数。
我们必须非常小心 List
和 Count()
,因为 List
类以及可能存在的许多其他集合中还有一个属性。所以在使用 Count()
时,我们不能直接对列表进行查询。
我这里做的是一个查询结果,然后在原始查询返回结果后调用 Count()
。
LongCount
LongCount
运算符计算序列中的元素数量。
public static long LongCount<T>(
this IEnumerable<T> source);
public static long LongCount<T>(
this IEnumerable<T> source,
Func<T, bool> predicate);
LongCount
运算符枚举源序列并计算满足谓词函数返回 true 的元素的数量。如果未指定谓词函数,则 LongCount
运算符简单地计算所有元素。元素计数作为 long 类型的值返回。
其工作方式与 Count
相同,但返回一个 long。
Sum
Sum
运算符计算数值序列的和。
public static Numeric Sum(
this IEnumerable<Numeric> source);
public static Numeric Sum<T>(
this IEnumerable<T> source,
Func<T, Numeric> selector);
Sum
运算符枚举源序列,为每个元素调用选择器函数,并计算结果值的总和。如果未指定选择器函数,则计算元素本身的和。
那么,让我们看一个实际的例子 (使用附带的演示项目和 _itemList
List
本地数据源)
var totals = (from i in _itemList select i.UnitPrice).Sum();
此示例简单地对所有 Item
的 UnitPrice
求和。
最小值
Min
运算符查找数值序列的最小值。
public static Numeric Min(
this IEnumerable<Numeric> source);
public static T Min<T>(
this IEnumerable<T> source);
public static Numeric Min<T>(
this IEnumerable<T> source,
Func<T, Numeric> selector);
public static S Min<T, S>(
this IEnumerable<T> source,
Func<T, S> selector);
Min
运算符枚举源序列,为每个元素调用选择器函数,并查找结果值的最小值。如果未指定选择器函数,则计算元素本身的最小值。使用它们实现的 IComparable<T> 接口,或者,如果值未实现该接口,则使用非泛型 IComparable 接口来比较这些值。
那么,让我们看一个实际的例子 (使用附带的演示项目和 _itemList
List
本地数据源)
var minimum = (from i in _itemList select i.UnitPrice).Min();
此示例简单地获取所有 Item
的最小 UnitPrice
。
最大值
Max 运算符查找数值序列的最大值。
public static Numeric Max(
this IEnumerable<Numeric> source);
public static T Max<T>(
this IEnumerable<T> source);
public static Numeric Max<T>(
this IEnumerable<T> source,
Func<T, Numeric> selector);
public static S Max<T, S>(
this IEnumerable<T> source,
Func<T, S> selector);
Max
运算符枚举源序列,为每个元素调用选择器函数,并查找结果值的最大值。如果未指定选择器函数,则计算元素本身的最小值。使用它们实现的 IComparable<T> 接口,或者,如果值未实现该接口,则使用非泛型 IComparable 接口来比较这些值。
Max
的工作方式与 Min
相同,我将其留给读者练习。
Average
Average
运算符计算数值序列的平均值。
public static Result Average(
this IEnumerable<Numeric> source);
public static Result Average<T>(
this IEnumerable<T> source,
Func<T, Numeric> selector);
Average
运算符枚举源序列,为每个元素调用选择器函数,并计算结果值的平均值。如果未指定选择器函数,则计算元素本身的平均值。
Max
的工作方式与 Min 相同,我将其留给读者练习。
Aggregate
Aggregate
运算符对序列应用函数。
public static T Aggregate<T>(
this IEnumerable<T> source,
Func<T, T, T> func);
public static U Aggregate<T, U>(
this IEnumerable<T> source,
U seed,
Func<U, T, U> func);
带种子值的 Aggregate
运算符首先将种子值分配给内部累加器。然后,它枚举源序列,通过调用指定的函数,将当前累加器值作为第一个参数,将当前序列元素作为第二个参数,反复计算下一个累加器值。最终的累加器值作为结果返回。
我必须说,这个让我失败了。我到处搜索另一个例子,因为 LINQ 文档中的例子非常糟糕。看看吧,这个是直接来自 LINQ 文档的。
var longestNamesByCategory =
products.
GroupBy(p => p.Category).
Select(g => new {
Category = g.Key,
LongestName =
g.Group.
Select(p => p.Name).
Aggregate((s, t) => t.Length > s.Length ? t : s)
});
这不是一个很好的例子,对吧?这就是我们从 LINQ 中得到的东西。它非常强大,但其中一些是纯粹的疯狂语法。我的意思是,上面的例子到底在告诉别人什么?对我来说不清楚。即使是出色的 101 LINQ 示例也没有列出 Aggregate
运算符的示例。所以我想我们目前只能一带而过。
Fold
Fold 是一个很好的概念,直接来自函数式编程世界,它允许我们将一个新函数“折叠”到列表 (IEnumerable<T>
) 的元素中。这非常强大。
让我们来看一个例子
double[] doubles = { 2,4,6,8,10 };
double product = doubles.Fold((runningProduct,
nextFactor) => runningProduct * nextFactor);
在这个简单的例子中,我们有一个数组,我们想得到它的乘积。我们可以简单地使用 fold 来字面地将一些内联函数 (即 runningProduct * nextFactor
) “折叠”起来形成结果。很巧妙,是吧?
好了,这就是关于标准查询运算符的内容了,如果您能坚持到这里,做得很好。我花了很长时间才写完这篇文章,您可能花了很长时间才读完。所以,如果您想稍后再回来,我不会怪您。但接下来的部分是关于动态创建 (在运行时) 的查询。到目前为止,我们所做的都是预编译的查询,这都很好,但并不完全现实。在现实世界中,我们希望能做动态查询,对吧?
动态创建 LINQ 查询
到目前为止,我们已经研究了静态 (在编译时定义的) 查询,这都很好,但并不完全是我们可能想为实际应用程序做的事情。
还可以通过编程方式创建 LINQ 查询,使用用户可能从 UI 输入或选择的信息。这可以通过以下原则来实现
通过使用变量
我们可以简单地在查询中引入变量,如下所示
decimal priceVal = 50.00M;
//query will now use a variable so is dynamic
var iSmall = from it in _itemList
where it.UnitPrice < priceVal
select it;
因此,通过引入一个简单的变量,我们可以控制查询。很简单。
QueryExpression 类型 (主要用于 DLINQ)
QueryExpression
,根据 MSDN 文档,它只有一个属性 ExpressionOperator
,用于获取或设置表达式中使用的运算符。
然而,如果我们查看 Visual Studio 2005 (假设您安装了 May 2006 LINQ CTP),我们会得到一个更清晰的图景。
从这个图中可以看到,我们可以提供任何我们想要的 ExpressionOperator
。这是在运行时创建动态 LINQ 查询的秘密的一部分。
所以,我们可以做类似的事情 (特别注意过滤器的使用,它可以是任何字符串)
//where filter is a string which could be set to "city = 'London'"
string filter = "city = 'London'";
expression = QueryExpression.Where(expression,
QueryExpression.Lambda(filter, e));
//Finally, we create a new query based on that expression.
//where db inherits from DataContent and was automatically generated using
//the DLINQ tool Sqlmetal.exe
var query = db.CreateQuery<EmployeeView>(expression);
这个例子实际上是一个 DLINQ 查询。但这应该可以在标准 LINQ 中使用新的 Queryable<T>
来实现,它在 March 2007 CTP 中是新加入的,但我没有安装那个 CTP,而且可能也不会安装,因为它有 4GB (是整个“Orcas”构建),而且肯定还会再次更改。
作为 May 2006 CTP 的一部分安装的 InteractiveQuery 项目是查找动态 DLINQ 查询的好地方。
IQuerablable 接口
要在内存对象上执行运行时查询 (LINQ),您需要新的 March 2007 CTP,它允许用户通过使用新的 Queryable<T>
功能来实现这一点。
The Wayward WebLog 展示了以下示例来实现这一点。我建议您阅读一下。
IQueryable q = ...;
ParameterExpression p = Expression.Parameter(typeof(Customer), "c");
LambdaExpression predicate =
QueryExpression.Lambda("c.City = 'London'", p);
Expression where = QueryExpression.Where(q.Expression, predicate);
q = q.CreateQuery(where);
我后来与 The Wayward WebLog 的作者 Matt Warren 联系过,他给我发了一封不错的邮件 (看,我一直在努力帮助大家)。所以,我将解释他告诉我的内容。
如果我们设想有一个方法,比如
IQueryable Between(IQueryable query, int min, int max)
{
return from n in query where n >= min && n <= max select n;
}
现在我们有了查询 IQuerable
对象的设施。请记住,IQuerable
仅在新的 March 2007 CTP 中可用。所以,让我们深入研究一下。我们有一个 nice method 在那里,它允许我们对 IQuerable
对象执行某种动态查询。所以,让我们看看如何调用这个新方法。
假设我们有一个简单的数组
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
然后我们像这样查询这个数组
IEnumerable<int> query =
from n in numbers
where n %2 == 1
select n;
好的,现在我们得到了一个查询结果,它是 IEnumerable<int>
类型。太棒了。但是我们现在可以做这样的事情
IQueryable<int> query2 = Between(query.AsQueryable(), 0, 10);
这是怎么回事?嗯,我们重用了第一个查询的结果,它产生了一个 IEnumerable<int>
,然后我们使用 .AsQueryable()
将 IEnumerable<int>
的结果转换为一个 IQuerable
对象。这一切都归功于新的 IQuerable
接口,它现在是 March 2007 CTP 的一部分。运行时管理在 IEnumerable 之上构建的 IQuerable 查询的执行。
我相信您会同意,我们迟早都要学会这样做。我个人打算让 CTP 再成熟一些,安装说明也更清晰一些 (当前的 CTP 是针对整个“Orcas”项目,这是 Visual Studio 的下一个版本,所以它非常庞大)。但这只是我的个人意见,如果您迫不及待想进行动态查询并一窥“Orcas”的真容,那么就下载 March 2007 CTP。
就这些
好了,差不多就这些了。正如我所说,这篇文章可能没有展示太多您从访问 101 LINQ Samples 中学不到的东西,但是,所有信息都必须来自某个地方。也许有些人不知道 101 LINQ Samples,甚至不知道 LINQ,除非他们读了这篇文章,在这种情况下,我可能帮了他们一个忙。此外,本文确实展示了实际工作的代码,而我发现 LINQ 文档中的一些 LINQ 示例根本不起作用,或者过于复杂,可能会吓到一些人。我真的试图让本文中的示例尽可能简单。
接下来的两篇文章 (XLlNQ / DLINQ) 应该会展示一些对大家有用的新材料……我保证我会努力覆盖新材料。
您怎么看?
我想问一下,如果您喜欢这篇文章,请投票支持它,因为它能让我知道文章的水平是否合适。
此外,如果您认为接下来两篇计划中的文章应该包含这么多内容还是更少的内容。请告诉我,毕竟我想写真正能帮助人们的文章。我知道这篇文章包含了很多内容。但它是 LINQ/DLINQ/XLINQ 中常见的,所以不得不涵盖大量信息。总之,请告诉我您的想法。
结论
我非常享受构建这篇文章的过程,并且对我能够轻松使用 LINQ (嗯,大部分是这样,有些 Group 和 Aggregate 运算符简直是糟糕透顶) 感到非常欣慰。我还产生了一种怀旧之情,因为它让我想起了 Haskell 编程,我强烈建议大家避免这样做,因为它简直是疯狂的。但如果你喜欢 Lambda,那么你应该会喜欢 Currying 和 Lazy Evaluation 以及所有那种函数式的东西。它实际上相当不同。
历史
v1.0 2007/03/23: 首次发布