支持 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 此方法可以创建匿名类型,但仅适用于局部变量。 有关详细信息,请参阅 隐式类型局部变量

对象和集合初始值设定项

对象和集合初始值设定项可以初始化对象,而无需显式调用对象的构造函数。 初始值设定项通常在查询表达式中将源数据投影到新的数据类型时使用。 假设具有公共CustomerName属性的类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);

查询 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);
}