支持 LINQ 的 C# 功能
查询表达式
查询表达式使用类似于 SQL 或 XQuery 的声明性语法来查询 System.Collections.Generic.IEnumerable<T> 集合。 在编译时,查询语法转换为对 LINQ 提供程序的标准查询方法实现的方法调用。 应用程序通过使用 using
指令指定适当的命名空间来控制范围内的标准查询运算符。 下面的查询表达式获取一个字符串数组,按字符串中的第一个字符对字符串进行分组,然后对各组进行排序。
var query = from str in stringArray
group str by str[0] into stringGroup
orderby stringGroup.Key
select stringGroup;
隐式类型化变量 (var)
可以使用 var 修饰符来指示编译器推断和分配类型,如下所示:
var number = 5;
var name = "Virginia";
var query = from str in stringArray
where str[0] == 'm'
select str;
声明为 var
的变量与显式指定其类型的变量一样都是强类型。 通过使用 var
,可以创建匿名类型,但只能用于本地变量。 有关详细信息,请参阅隐式类型局部变量。
对象和集合初始值设定项
通过对象和集合初始值设定项,初始化对象时无需为对象显式调用构造函数。 初始值设定项通常用在将源数据投影到新数据类型的查询表达式中。 假定一个类名为 Customer
,具有公共 Name
和 Phone
属性,可以按下列代码中所示使用对象初始值设定项:
var cust = new Customer { Name = "Mike", Phone = "555-1212" };
继续 Customer
类,假设有一个名为 IncomingOrders
的数据源,并且每个订单具有一个较大的 OrderSize
,你希望基于该订单创建新的 Customer
。 可以在此数据源上执行 LINQ 查询,并使用对象初始化来填充集合:
var newLargeOrderCustomers = from o in IncomingOrders
where o.OrderSize > 5
select new Customer { Name = o.Name, Phone = o.Phone };
数据源可能具有比 Customer
类(例如 OrderSize
)定义的更多的属性,但执行对象初始化后,从查询返回的数据被定型为所需的数据类型;选择与你的类相关的数据。 因此,你现在有填充了想要的多个新 Customer
的 System.Collections.Generic.IEnumerable<T>。 前面的示例还可以使用 LINQ 的方法语法编写:
var newLargeOrderCustomers = IncomingOrders.Where(x => x.OrderSize > 5).Select(y => new Customer { Name = y.Name, Phone = y.Phone });
从 C# 12 开始,可以使用集合表达式来初始化集合。
有关详细信息,请参阅:
匿名类型
编译器构造匿名类型。 该类型名称仅适用于编译器。 匿名类型提供一种在查询结果中对一组属性临时分组的简便方法,无需定义单独的命名类型。 使用新的表达式和对象初始值设定项初始化匿名类型,如下所示:
select new {name = cust.Name, phone = cust.Phone};
从 C# 7 开始,可以使用元组创建未命名的类型。
扩展方法
扩展方法是一种可与类型关联的静态方法,因此可以像实例方法那样对类型调用它。 实际上,利用此功能,可以将新方法“添加”到现有类型,而不会实际修改它们。 标准查询运算符是一组扩展方法,它们为实现 IEnumerable<T> 的任何类型提供 LINQ 查询功能。
Lambda 表达式
Lambda 表达式是一种内联函数,该函数使用 =>
运算符将输入参数与函数体分离,并且可以在编译时转换为委托或表达式树。 在 LINQ 编程中,在对标准查询运算符进行直接方法调用时,会遇到 lambda 表达式。
作为数据的表达式
查询对象可编写,这意味着你可以从方法中返回查询。 表示查询的对象不会存储生成的集合,但会根据需要存储生成结果的步骤。 从方法中返回查询对象的好处是可以进一步编写或修改这些对象。 因此,返回查询的方法的任何返回值或 out
输出参数也必须具有该类型。 如果某个方法可将查询具体化为具体的 List<T> 或 Array 类型,则它会返回查询结果,而不是查询本身。 仍然能够编写或修改从方法中返回的查询变量。
在下面的示例中,第一个方法 QueryMethod1
返回了一个查询作为返回值,第二个方法 QueryMethod2
返回了一个查询作为 out
参数(在本例中为 returnQ
)。 在这两种情况下,返回的都是查询,而不是查询结果。
IEnumerable<string> QueryMethod1(int[] ints) =>
from i in ints
where i > 4
select i.ToString();
void QueryMethod2(int[] ints, out IEnumerable<string> returnQ) =>
returnQ =
from i in ints
where i < 4
select i.ToString();
int[] nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
var myQuery1 = QueryMethod1(nums);
查询 myQuery1
在以下 foreach 循环中执行。
foreach (var s in myQuery1)
{
Console.WriteLine(s);
}
将鼠标指针悬停在 myQuery1
上方以查看其类型。
还可以直接执行从 QueryMethod1
返回的查询,而无需使用 myQuery1
。
foreach (var s in QueryMethod1(nums))
{
Console.WriteLine(s);
}
将鼠标指针悬停在对 QueryMethod1
的调用上以查看其返回类型。
QueryMethod2
会返回一个查询作为其 out
参数的值:
QueryMethod2(nums, out IEnumerable<string> myQuery2);
// Execute the returned query.
foreach (var s in myQuery2)
{
Console.WriteLine(s);
}
可以使用查询组合来修改查询。 在这种情况下,前一个查询对象用于创建新的查询对象。 此新对象会返回与原始查询对象不同的结果。
myQuery1 =
from item in myQuery1
orderby item descending
select item;
// Execute the modified query.
Console.WriteLine("\nResults of executing modified myQuery1:");
foreach (var s in myQuery1)
{
Console.WriteLine(s);
}