LINQ & Lambda 表达式基础






4.76/5 (122投票s)
使用 C# 3.0 和/或 .NET 3.5 中引入的 Language Integrated Query (LINQ) 概念的基本示例(包含相应的 VB.NET 代码)
- 引言
- 什么是LINQ?
- LINQ 类型.
- Lambda 表达式
- 扩展方法
- 什么是 Yield?
- 匿名类型
- Var 的含义
- Linq 演示(带示例)
- 运算符
- DLINQ(LINQ to SQL)
- XLINQ(LINQ to XML)
- 参考
- 结论
- 历史
介绍
各位朋友,距离我上次写文章已经很久了。在此期间,我学到了很多东西,现在是时候与大家分享了。.NET 随着新概念的不断涌现而变得越来越丰富。其中最受欢迎的莫过于在基本应用程序数据中引入了查询集成。
LINQ
Linq 是微软首次尝试将查询集成到语言中。我们知道,通过编写查询可以轻松地从 SQL 对象中查找数据,但当我们在 DataTable 或 List 中执行相同的操作时,会有些麻烦。通常,我们需要遍历每个元素来查找精确匹配,如果需要聚合,则需要聚合值等。Linq 提供了一种简单的方式来编写可以与内存中对象一起运行的查询。
LINQ 类型
Linq 包含 3 种基本类型(假定在不同类型的对象上有更多 LINQ 类型)
1. LINQ (Linq to Objects)
2. DLINQ (Linq to SQL)
3. XLINQ (Linq to XML)
在继续讲解 LINQ 之前,我们必须了解一些可能会非常有用的新特性。
Lambda 表达式
Linq 总是与 Lambda 表达式相关联。在 .NET 2.0 中,我们有匿名方法(Annonymous Methods)的概念,它允许您在行内编写函数体,而无需编写委托函数。 .NET 3.5 的 Lambda 表达式是为了简化匿名函数编写的概念。
让我们看一个在 C# 2.0 中如何编写匿名方法的例子。
int i = this.compute(10); private int compute(int value) { return (value + 2); }
这是一个非常普通的函数调用示例。当调用 `i=this.compute(10)` 这行代码时,会调用 `compute` 函数。我们可以用行内匿名函数替换上面的代码,如下所示:
//C# delegate int DelType(int i); DelType dd = delegate(int value) { return (value +2); }; int i = dd(10);
在第二种情况下,我们可以看到,只需声明一个委托并提供该委托类型的实例,就可以轻松地编写匿名方法。.NET 3.5 将这个概念进一步精简。我们可以使用 .NET 3.5 中引入的 `=>` 运算符。它提供了一种更灵活、更方便的方式来编写表达式。在前面的情况下,可以使用 Lambda 表达式来获得结果,如下所示:
delegate int DelType(int i); DelType d = value => value + 2; int i = d(10);
因此,委托类型被直接赋值。`value =>value + 2` 这行代码的含义与声明一个函数相似。第一个 `value` 表示我们传递给函数的参数,`"=>"` 运算符后面的部分是函数的体。同样,如果需要,我们可以传入任意数量的参数。在以下示例中:
// Method with 2 arguments delegate int DelType(int i, int j); DelType d = (value1,value2) => value1 + value2; int i = d(10,20) // Returns 30
在此示例中,我们向函数传递了 2 个参数并返回结果。您可能还想知道,当函数体需要多个表达式时,如何编写 Lambda 表达式。别担心,很简单。请看下面的示例:
// Multiline Function Body delegate int DelType(int i, int j); DelType d = (value1,value2) => { value1 = value1 + 2; value2 = value2 + 2; return value1 + value2; };
因此,我们看到编写 Lambda 表达式非常简单。这在处理 Linq 时非常方便,因为 Linq 的大多数扩展方法都接受函数委托作为参数。我们可以轻松地将简单的委托函数体传递给这些函数,以方便地执行工作。
这些类型的表达式也称为表达式树(Expression Trees),因为这些表达式的基本单位是数据,并且以树形结构组织。如果愿意,我们还可以使用 `Expression` 类自己创建动态表达式类型。
Expression<Func<int, int>> exprTree = num => num * 5; // Decompose the expression tree. ParameterExpression param = (ParameterExpression)exprTree.Parameters[0]; BinaryExpression operation = (BinaryExpression)exprTree.Body; ParameterExpression left = (ParameterExpression)operation.Left; ConstantExpression right = (ConstantExpression)operation.Right;
因此,如果我们编写一个接收表达式树的函数,我们可以轻松地分解参数。
注意
VB.NET 目前不支持带有语句体的匿名方法和 Lambda 表达式。
扩展方法
.NET 3.5 带来的另一个新概念是扩展方法。现在我们可以向已定义的对像添加我们自己的自定义方法。我们可以创建静态类并将自定义方法添加到对像中。扩展方法的行为与静态方法类似。它们只能在静态类中声明。要声明扩展方法,请将 `this` 关键字指定为方法的第一个参数。让我们看下面的示例:
public static class ExtensionMethods { public static int ToInt32Extension(this string s) { return Int32.Parse(s); } }
如果我们将命名空间添加到我们的应用程序中,任何字符串变量都将具有 `ToInt32Extension` 方法。此函数接受 0 个参数并将字符串传递给 `s`。我们也可以像下面这样传递参数:
public static class ExtensionMethods { public static int ToInt32ExtensionAddInteger(this string s,int value) { return Int32.Parse(s) + value; } }
这里也传递了整数 `value`。
我们还可以创建接受函数作为参数的扩展方法。`Enumerable` 类扩展了许多方法,这些方法使用 `Func` 类将方法委托发送到扩展中,以便它可以与 `IEnumerable` 对象列表一起工作。让我们创建自己的扩展方法,它也能与 `IEnumerable` 一起工作,请看下面的代码:
public static int GetMinVal<TSource>(this IEnumerable<TSource> source, Func<TSource, int> selector) { int smallest = int.MaxValue; foreach (var item in source) { int nextItem = selector(item); if (nextItem < smallest) { smallest = nextItem; } } return smallest; } // calling the Function int minimum = employees.GetMinVal<Employee>(emp => emp.age);
现在,如果我调用此函数,它将返回最小值。选择器函数将是传入的匿名委托。因此,选择器将像这样调用函数:
public int selector(Employee emp) { return emp.age; }
因此,它将比较所有员工的年龄,并获得最小值。
与此类似,让我们自己编写 Where 函数:
public static IEnumerable<TSource> MyOwnWhere<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) where TSource : class { foreach (var item in source) { if (predicate(item)) yield return item; } }
这将调用我们传递给函数的谓词函数,如果函数返回 true,则允许它通过。因此,我们创建了一个返回源列表的功能,类似于 Enumerable 类中定义的扩展方法的 Where 子句。
Yield 是什么?
您可能注意到这里我使用了 `yield` 关键字来生成项目列表。这是 C# 2.0 的一项新功能。此关键字的含义是指示程序运行并生成列表,直到所有循环执行都完成。因此,它为谓词函数返回 true 的项创建了一个列表。
对于 VB.NET 开发者
对于 VB.NET,我们必须在带有属性的 Module 中定义扩展方法:<System.Runtime.CompilerServices.Extension()>。
Module ExtensionModule <System.Runtime.CompilerServices.Extension()> _ Public Function ToInt32ExtensionAddInteger(ByVal s As String, _ ByVal value As Integer) As Integer Return Int32.Parse(s) + value End Function End Module
匿名类型
.NET 中引入的匿名类型可以在不声明类的情况下创建对象。匿名类型的成员在编译时被隐式定义。
var x = new { Name ="AA", Age =30, ... };
这将创建一个新的匿名类型对象。
Var 的含义
Var 是 .NET 3.5 添加的一个新关键字。它被称为隐式类型变量。在运行时,它会根据找到的对象自动为其变量分配类型。
var x = 10 // implies the variable is of integer type var x = new list<Employee>(); // Means x holds list of employees
`var` 在使用刚刚引入的匿名类型时非常有用。它根据传入对象的类型为变量分配类型,因此,执行 `List
唯一的限制是我们不能定义 var 类型的成员。
Linq 演示
现在,让我们通过一个简单的示例来演示 LINQ。该示例可以在本文附带的源代码中找到。
首先,我们定义:
C# List<Employee> employees = new List<Employee>(); List<Employee> employee1 = new List<Employee>(); List<Order> orders = new List<Order>(); //Where Employee and Order are classes. public class Employee { public string name; public int age; public override string ToString() { return this.name; } } public class Order { public string itemName; public string empName; public override string ToString() { return this.itemName; } } VB.NET Private employees As New List(Of Employee)() Private employee1 As New List(Of Employee)() Private orders As New List(Of Order)() //Where Employee and Order are classes. Public Class Employee Public name As String Public age As Integer Public Overloads Overrides Function ToString() As String Return Me.name End Function End Class Public Class Order Public itemName As String Public empName As String Public Overloads Overrides Function ToString() As String Return Me.itemName End Function End Class
我们在示例中将使用这三个列表。
运算符
在编写查询时,您会发现可以使用许多运算符。最常用的运算符是:- 投影运算符(Select)
- 筛选运算符(Where)
- 分区运算符(Take / Skip, TakeWhile / SkipWhile)
- 连接运算符(Join)
- 连接运算符(Concat)
- 排序运算符(orderby)
- 分组运算符(group by into)
投影运算符
通过投影,我指的是 Select 语句。每个 linq 元素都应包含投影。
C#
var iNames = from i in employees
select i.name;
//Using Lambda Expression
var iNames = employees.Select<Employee, string>(r => r.name);
VB.NET
Dim iNames = From i In employees _
Select i.name
这里返回的是员工姓名(IEnumerable)。在上面的示例中,我们还利用了 .NET 3.5 中引入的匿名类型声明。
筛选运算符
可以使用 `Where` 子句应用筛选运算符。Where 子句的示例:
var filterEnumerable = from emp in employees
where emp.age > 50
select emp;
//Using Lambda Expression
var filterEnumerable = employees.Where<Employee>(emp => emp.age > 50);
Vb.NET
Dim filterEnumerable = From emp In employees _
Where emp.age > 50 _
Select emp
这会根据年龄大于 50 的员工进行筛选。**
使用 Take / Skip 运算符进行分区
`Take` 用于获取列表中的前 N 个元素,`skip` 用于获取 N 个元素之后的元素。
var filterEnumerable = (from emp in employees
select emp).Take<Employee>(2);
//Using Lambda Expression
var filterEnumerable = employees.Select<Employee, string>(r => r.name);
VB.NET
Dim filterEnumerable = (From emp In employees _
Select emp).Take(Of Employee)(2)
**Takewhile 和 skipwhile** 运算符将根据传入的委托从列表中选择。
var filterEnumerable = (from emp in employees select emp).SkipWhile<Employee>( r => r.name.Length > 4); //Using Lambda Expression var filterEnumerable = employees.Select<Employee, Employee>( r => { return r; }).SkipWhile<Employee>(r => r.name.Length > 4);
VB.NET Dim filterEnumerable = (From emp In employees _ Select emp).SkipWhile(Of Employee)(Function(r) r.name.Length > 4)
连接运算符
连接运算符有两个部分。外部部分从内部部分获取结果,反之亦然,因此根据两者返回结果。var filterEnumerable = from emp in employees
join ord in orders
on emp.name
equals ord.empName
select emp;
//Using Lambda Expression
var filterEnumerable = employees.Join<Employee, Order, string, Employee>
(orders, e1 => e1.name, o => o.empName, (o, e2) => o);
VB.NET
Dim filterEnumerable = From emp In employees _
Join ord In orders On emp.name = ord.empName _
Select emp
在这里,我们连接员工并根据传入的姓名进行排序。 连接运算符
连接运算符连接两个序列。var filterEnumerable = (from emp in employees
select emp).Concat<Employee>(
from emp in employees1
select emp);
//Using Lambda Expression
var filterEnumerable = employees.Concat<Employee>(
employees1.AsEnumerable<Employee>());
VB.NET
Dim filterEnumerable = (From emp In employees _
Select emp).Concat(Of Employee)(From emp In employees1 _
Select emp)
这将连接“娱乐”和“食品”类别。“Distinct”运算符也可用于评估结果集中的唯一元素。 OrderBy / ThenBy
OrderBy/ThenBy 可用于排序数据结果。var orderItems = from emp in employees orderby emp.name, emp.age descending; //Using Lambda Expression var orderItems =employees.OrderBy(i => i.name).ThenByDescending(i => i.age);
VB.NET Dim filterEnumerable = From emp In employees Order By emp.name, emp.age Descending
这里按姓名排序,然后按年龄降序排序。
GroupBy 运算符:
这用于对元素进行分组。
var itemNamesByCategory = from i in _itemList
group i by i.Category into g
select new { Category = g.Key, Items = g };
VB.NET
Dim itemNamesByCategory = From i In _itemList _
Group i By i.Category _
Into g _
Select g.Key
这会获取所有按类别分组的类别和项目。嗯,这个分组对我来说似乎有点棘手。让我向您解释一下确切的方法。在这里,当我们进行分组时,我们将组放入 g(它是一个 `IGrouping
var filterEnumerable2 = from emp in employees where emp.age >65 //Normal Where clause works on all items group emp by emp.age into gr where gr.Key > 40 select new { aaa = gr.Key, ccc=gr.Count<employee>(), ddd=gr.Sum<employee>(r=>r.age), bbb = gr };
在这里,我返回了聚合函数,如 sum、count 等,您可以在组数据中找到这些函数。
Distinct / Union / Intersect / Except 运算符
**Union 运算符**生成两个序列的并集。
var un = (from i in _itemList
select i.ItemName).Distinct()
.Union((from o in _orderList
select o.OrderName).Distinct());
**Intersect 运算符**生成两个序列的交集。var inter = (from i in _itemList
select i.ItemID).Distinct()
.Intersect((from o in _orderList
select o.OrderID).Distinct());
VB.NET
Dim inter = (From i In _itemList _
Select i.ItemID).Distinct().Intersect((From o In _orderList _
Select o.OrderID).Distinct())
**Except 运算符**生成两个序列的差集元素。
var inter = (from i in _itemList
select i.ItemID).Distinct()
.Except((from o in _orderList
select o.OrderID).Distinct())
Let 的使用
我们可以使用 `let` 关键字在 LINQ 语句中创建临时变量。
var filterEnumerable = from emp in employees let x = emp.age let y = x * 5 select x;
VB.NET
Dim filterEnumerable = From emp In employees _
Let x = emp.age _
Let y = x * 5 _
Select x
在这里,x 和 y 是中间变量,我们将得到每个员工年龄 5 倍的整数的可枚举集合。
DLINQ
DLINQ 或 LINQ to SQL 提供了一个运行时框架来处理关系数据库对象。它会自动将 LINQ 查询转换为 SQL 查询。我们可以使用 LINQ 对象创建数据库的完整快照,以便可以在应用程序类和对象中创建整个关系。有一些工具可以自动创建这些对象,我们可以在程序中使用它们。我们可以使用 `DataContext` 类来体验 DLINQ。 .NET 使用增强的属性来定义类。
让我们看一个例子
[Table(Name="Employee")] public class Employee { [Column(Id=true)] public string EmpID; [Column] public string Name; }
这意味着 Employee 类对应实际的 Employee 表。其中 `EmpID` 是表的主键,Name 是另一个列。
如需进一步阅读
MSDN 上的 LINQ to SQL
XLINQ
与 DLINQ 类似,XLINQ 指的是使用**LINQ to XML**文档。 .NET 扩展了 LINQ 这个小而精美的工具,创建了许多可以轻松操作 XML 文档的类。与 XML DOM 相比,XLINQ 更具灵活性,因为它不需要您始终拥有文档对象才能处理 XML。因此,您可以直接处理节点并将其作为文档内容进行修改,而无需从根 `XmlDocument` 对象开始。这是一个非常强大且灵活的功能,可用于从树片段组合更大的树和 XML 文档。让我们看下面的示例:
XDocument bookStoreXml = new XDocument( new XDeclaration("1.0", "utf-8", "yes"), new XComment("Bookstore XML Example"), new XElement("bookstore", new XElement("genre", new XAttribute("name", "Fiction"), new XElement("book", new XAttribute("ISBN", "10-861003-324"), new XAttribute("Title", "A Tale of Two Cities"), new XAttribute("Price", "19.99"), new XElement("chapter", "Abstract...", new XAttribute("num", "1"), new XAttribute("name", "Introduction")), new XElement("chapter", "Abstract...", new XAttribute("num", "2"), new XAttribute("name", "Body") ) ) ) ) );
在此示例中,我们使用 `XDocument` 类创建了一个 XML 文档。`XDocument` 类可能包含 `XElements`,它们是 XML 元素。XDeclaration 是创建 XML 声明的类。`XAttribute` 创建 XML 属性。`XComment` 创建 XML 注释。在此示例中,根元素 bookstore 包含其内的所有子元素。我们可以将整个 `XElement` 树传递到任何 `XElement` 中,因此它提供了一种更简单的方式来创建 XML 文档。
在读取、创建或操作 XML 文档后,我们可以使用 XDocument 对象上的 save 方法轻松地将其保存到磁盘。bookStoreXml.Save(@"C:\XMLDocuments\Bookstore.xml");
这将把整个文档保存到磁盘。
要再次读取文档并在应用程序端创建完整的对象树结构,我们可以调用 XElement 的 load 方法。XElement.Load(@"C:\XMLDocuments\Bookstore.xml");
如果想从字符串加载 XML,我们也可以使用 parse 方法。
您可以以与在对象中相同的方式查询 XML 文档。我们可以使用一些方法:`Ancestors`(返回 `IEnumerable` 列表中的所有祖先元素)、`Descendants`(返回元素的子孙)、`Element`(返回名为子元素的元素)、`Elements`(返回当前节点的子元素)、`Nodes`(返回当前节点的内容)。
如需进一步阅读
MSDN 上的 XLINQ
参考文献
如需进一步阅读,您可以查阅以下链接:
1. Charlie Claverts 的博客
2. MSDN 上的 Linq 表达式文章
3. MSDN 上的 LINQ 文章
结论
因此,使用 Linq,我们可以轻松处理复杂应用程序数据中的搜索,从而大大减少循环和条件语句。
历史
这是 2009 年 3 月 1 日的第一个版本。