查询表达式
查询表达式使用类似于 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,但使用对象初始化,从查询返回的数据将塑造成所需的数据类型;选择与类相关的数据。 因此,你现在有填充了想要的多个新 System.Collections.Generic.IEnumerable<T> 的 Customer。 还可以在 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);
以下 foreach 循环执行查询 myQuery1。
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);
}