LINQ查询
var names = new List<string> { "Nino", "Alberto", "Juan", "Mike", "Phil" };
var namesWithJ = from n in names where n.StartsWith("J") orderby n select n;
foreach (string name in namesWithJ)
{
WriteLine(name);
}
var query = from n in names where n.StartsWith("J") orderby n select n;
变量query只指定了LINQ查询,但不执行。
查询表达式必须以from子句开头,以select或group子句结束。
拓展方法
编译器会将LINQ查询转换成相对应的扩展方法。LINQ为IEnumberable<T>接口提供了各种扩展方法,以便于用户在实现了该接口的任意集合上使用LINQ查询。拓展方法在静态类中声明,定义为一个静态方法,其中第一个参数定义了它的扩展类型。
要使用拓展方法,只需要导入包含该类的名称空间。
定义LINQ拓展方法的一个类是System.Linq名称空间中的Enumerable。只需要导入这个名称空间,就可以打开这个类的扩展方法的作用域。
var query = names.Where(n => n.StartsWith("J")).OrderByDescending(n=>n).Select(n=>n);
Where扩展方法的实现代码
Where扩展方法的第一个参数包含了this关键字,其类型是IEnumerable<T>。这样,Where方法就可以用于实现IEnumerable<T>的每个类型。例如List<T>。第二个参数是一个Function<T, bool>委托,它引用了一个返回布尔值、参数类型为T的方法。这个谓词在实现代码中调用,检测IEnumerable<T>源中的项是否应放在目标集合中。如果委托引用了该方法,yield return语句就将源中的项返回给目标。
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
foreach (TSource item in source)
if(predicate(item)
yield return item;
}
LINQ标准查询操作符
标准查询操作符 | 说明 |
---|---|
Where OfType<TResult> | 筛选操作符定义了返回元素的条件
在Where查询操作符中可以使用谓词,例如,lambda表达式定义的谓词,来返回布尔值。 OfType<TResult>根据类型筛选元素,只返回TResult类型的元素 |
Select SelectMany | 投射操作符用于把对象转换为另一个类型的新对象。
Select和SelectMany定义了根据选择器函数选择结果值的投射 |
OrderBy ThenBy OrderByDescending ThenByDescending Reverse | 排序操作符改变所返回的元素顺序。
OrderBy 升序排列 OrderByDescending 降序排序
如果第一次排序的结果很类似,可以二次排序 ThenBy 升序排列 ThenByDescending 降序排序
Reverse 翻转排序 |
Join GroupJoin | 连接操作符用于合并不直接相关的集合。
Join 可以根据键选择器函数连接两个集合 GroupJoin 连接两个集合,组合其结果 |
GroupBy ToLookup | 组合操作符把数据放在组中
GroupBy 组合有公共键的元素 ToLookup 通过过创建一个一对多字典,来组合元素 |
Any All Contains | 限定符返回布尔值。
Any 确定集合中是否有满足谓词函数的元素 All 确定攻击和中的所有元素是否都满足谓词函数 Contains 检查某个元素是否在集合中 |
Take Skip TakeWhile ShipWhile | 分区操作符返回集合的一个子集。
Take 必须指定要从集合中提取的元素个数 Skip 跳过指定元素的个数,提取其他元素 TakeWhile 提取条件为真的元素 SkipWhile 跳过条件为真的元素 |
Distinct Union Intersect Except Zip | Set操作符返回一个集合
Distinct 从集合中删除重复的元素 Union 返回出现在其中一个集合中的唯一元素 Intersect 返回两个集合中都有的元素 Except 返回只出现一个集合中的元素 Zip 把两个集合合并为一个 |
First FirstOrDefault Last LastOrDefault ElementAt ElementAtOrDefault Single SingleOrDefault | 元素操作符仅返回一个元素
First 返回第一个满足条件的元素 FirstOrDefault 类似于First,但如果没有找到满足条件的元素,就返回类型的默认值 Last 返回最后一个满足条件的元素 LastOrDefault 类似于Last,但如果没有找到满足条件的元素,就返回类型的默认值 ElementAt 指定为返回元素的位置 ElementAtOrDefault 类似于ElementAt,但如果没有找到满足条件的元素,就返回类型的默认值 Single 只返回一个满足条件的元素。如果有多个元素满足条件,就抛出一个异常 SingleOrDefault 类似于Single,但如果没有找到满足条件的元素,就返回类型的默认值 |
Count Sum Min Max Average Aggregate | 聚合操作符计算一个值
|
ToArray AsEnumerable ToList ToDictionary Cast<TResult> | |
Empty Range Repeat |
筛选
找出赢得至少15场比赛的巴西和奥地利赛车手。
var racers = from r in Formula1.GetChanpions()
where r.Wins > 15 && (r.Country == "Brazil" || r.Country == "Austria")
select r;
并不是所有的查询都可以用LINQ查询语法完成。也不是所有的扩展方法都映射到LINQ查询子句上。高级的查询需要使用扩展方法。
var racers = Formula1.GetChampions().Where(r=>r.Wins > 15 (r.Country == "Brazil" || r.Country == "Austria")).Select(r=>r);
用索引筛选
不能用LINQ查询的一个例子是Where方法的重载。在Where方法的重载中,可以传递第二参数——索引。索引是筛选返回的每个结果的计数器。可以在表达式中使用这个索引,执行基于索引的计算。
public class Program
{
public static void Main()
{
System.Console.WriteLine("============================================================");
System.Console.WriteLine("无索引筛选");
var racers = Formula1.GetChampions().Where((r,index)=>r.LastName.StartsWith("A")).Select(r=>r);
foreach (var r in racers)
{
System.Console.WriteLine(r);
}
System.Console.WriteLine("============================================================");
System.Console.WriteLine("获取索引");
var racers2 = Formula1.GetChampions().Where((r,index)=>r.LastName.StartsWith("A")).Select((r, index)=>new {r, index});
foreach (var r in racers2)
{
System.Console.WriteLine(r);
}
System.Console.WriteLine("============================================================");
System.Console.WriteLine("显示指定索引 0");
var racers3 = Formula1.GetChampions().Where((r,index)=>r.LastName.StartsWith("A") && index == 0);
foreach (var r in racers3)
{
System.Console.WriteLine(r);
}
System.Console.WriteLine("============================================================");
System.Console.WriteLine("显示指定索引 1");
var racers4 = Formula1.GetChampions().Where((r,index)=>r.LastName.StartsWith("A") && index == 1);
foreach (var r in racers4)
{
System.Console.WriteLine(r);
}
System.Console.WriteLine("============================================================");
System.Console.WriteLine("显示指定索引 2");
var racers5 = Formula1.GetChampions().Where((r,index)=>r.LastName.StartsWith("A") && index == 2);
foreach (var r in racers5)
{
System.Console.WriteLine(r);
}
System.Console.WriteLine("============================================================");
System.Console.WriteLine("显示指定集合位置 1");
var racers6 = Formula1.GetChampions().Where((r,index)=>r.LastName.StartsWith("A") && index == 1);
foreach (var r in racers6)
{
System.Console.WriteLine(r);
}
System.Console.WriteLine("============================================================");
System.Console.WriteLine("显示指定集合位置 14");
var racers7 = Formula1.GetChampions().Where((r,index)=>r.LastName.StartsWith("A") && index == 14);
foreach (var r in racers7)
{
System.Console.WriteLine(r);
}
System.Console.WriteLine("============================================================");
System.Console.WriteLine("显示指定集合位置 27");
var racers8 = Formula1.GetChampions().Where((r,index)=>r.LastName.StartsWith("A") && index == 27);
foreach (var r in racers8)
{
System.Console.WriteLine(r);
}
System.Console.WriteLine("============================================================");
System.Console.WriteLine("显示索引为偶数");
var racers9 = Formula1.GetChampions().Where((r,index)=>r.LastName.StartsWith("A") && index % 2 == 0);
foreach (var r in racers9)
{
System.Console.WriteLine(r);
}
}
}
运行结果如下:
问题(不理解的地方):
查出来的索引,对应于结果的序号。
如果指定索引的位置,对应于在原集合中的位置索引。
类型筛选
一般用于在混合类型的集合中筛选指定的类型。
object[] data = { "one", 2, 3, "four", "five", 6 };
var query = data.OfType<string>();
foreach (var s in query)
{
System.Console.WriteLine(s);
}
// 运行结果
one
four
five
复合from子句
如果需要根据对象的一个程序进行筛选,而该成员本身是一个系列,就可以使用符合的from子句。Racer类定义了一个属性Cars,其中Cars是一个字符串数组。要筛选驾驶法拉利的所有管局,可以使用如下所示的LINQ查询。第一个from子句访问从Formula1.GetChampions()方法返回的Racer对象,第二个from子句访问Racer类的Cars属性,以返回所有string类型的赛车。接着在where子句中使用这些晒车筛选驾驶法拉利的所有冠军。
var ferrariDrivers = from r in Formula1.GetChampions()
from c in r.Cars
where c == "Ferrari"
orderby r.LastName
select r.FirstName + " " + r.LastName;
foreach (var racer in ferrariDrivers)
{
System.Console.WriteLine(racer);
}
编译器把符合的from子句和LINQ查询转换为Select Many扩展方法。Select Many方法可用于迭代序列的序列。
public static IEnumerable<TResult> SelectMany<TSource, TCollection, TResult>(this IEnumerable<TSource> source,
Func<TSource, IEnumerable<TCollection>> collectionSelector,
Func<TSource, TCollection, TResult> resultSelector);
第一个参数是隐式参数,它从GetChanpions()方法中接收Racer对象序列。第二个参数是collectionSelector委托,其中定义了内部序列。在lambda表达式r=>r.Cars中,返回赛车集合。第三个参数是一个委托,现在为每个赛测调用该委托,接收Racer和Car对象。lambda表达式创建了一个匿名类型,它有Racer和Car属性。这个SelectMany方法的结果是摊平了赛车手和赛车的层次结构,为每辆赛车返回匿名类型的一个新对象集合。
这个新集合传递个Where方法,山选出驾驶法拉利的赛车手。最后,调用OrderBy和Select方法。
var ferrari drivers = Formular1.GetChampions().SelectMany(r => r.Cars, (r, c) => new { Racer = r, Car = c })
.Where(r => r.Car == "Ferrari")
.OrderBy(r => r.Racer.LastName)
.Select(r => r.Racer.FirstName + " " + r.Racer.LastName);
排序
按照赢得比赛的次数对赛车手进行降序排列。
var racers = from r in Formula1.GetChampions() where r.Country == "Brazil" orderby r.Wins descending select r;
var racers = Formula1.GetChampions().Where(r => r.Country == "Brazil").OrderByDescending(r => r.Wins).Select(r => r);
OrderBy和OrderByDescending方法返回IOrderEnumerable<TSource>。这个接口派生自IEnumerable<TSource>,但包含一个额外的方法CreateOrderEnumerable<TSource>。这个方法用于进一步非序列排序。如果根据关键字选择器来排序,其中有两项或两项以上相同,就可以使用ThenBy和ThenByDescending方法继续排序。这两个方法需要IOrderEnumerable<TSource>接口才能工作,但也返回该接口。所以,可以添加任意多个ThenBy和ThenByDescending方法,对集合进行排序。
使用LINQ查询时,只需要把所有用于排序的不同关键字(用逗号隔开)添加到orderby子句中。在下例中,所有的赛车手先按国家排序,再按照姓氏极性排序,最后按照名字排序。添加到LINQ查询结果中的Take扩展方法用于返回前10个结果。
var racers = (from r in Formula1.GetChampions() orderby r.Country, r.LastName, r.FirstName select r).Take(10);
var racers = Formula1.GetChampions().OrderBy(r=>r.Country).ThenBy(r=>r.LastName).ThenBy(r=>r.FisrtName).Take(10);
分组
要根据一个关键字值对查询结果分组,可以使用group子句。现按国家分组,并列出一个国家的冠军数。子句group r by e.Country into g 根据Country属性组合所有的赛车手,并定义一个新的标识符g,它以后用于访问分组的结果信息。group子句的结果根据应用到分组结果上的扩展方法Count来排序,如果冠军数相同,就根据关键字来排序,该关键字时国家,因为分组所使用的关键字。
var contries = from r in Formula1.GetChampions() group r by r.Country into g
orderby g.Count(), g.Key
where g.Count() >= 2 select new {Country = g.Key, Count = g.Count()};
var contries = Formula1.GetChampions().GroupBy(r => r.Country)
.OrderByDescending(g => g.Count())
.ThenBy(g => g.Key)
.Where(g => g.Count() >= 2)
.Select(g => new {Country = g.Key, Count = g.Count());
LINQ查询中的变量
在为分组编写的LINQ查询中,Count()方法调用了多次。使用let子句可以改变这种方式。let允许在LINQ查询中定义变量。
var contries = from r in Formula1.GetChampions() group r by r.Country into g
let count = g.Count()
orderby count, g.Key
where count >= 2 select new {Country = g.Key, Count = count};
var contries = Formula1.GetChampions().GroupBy(r => r.Country)
.OrderByDescending(g => g.Count())
.Select(g => new {Group = g, Count = g.Count()})
.ThenBy(g => g.Group.Key)
.Where(g => g.Count >= 2)
.Select(g => new {Country = g.Key, Count = g.Count);
对嵌套的对象分组
如果分组的对象应包含嵌套的序列,就可以改变select子句创建的匿名类型。在下面你的例子中,所返回的国家不仅应包含国家名和赛车手数据这两个属性,还应包含赛车手的名序列。这个序列赋予Racers属性的from/in内部子句指定,内部的from子句使用分组标识符g获得该分组中的所有赛车手,用姓氏对它们进行排序,在根据姓名创建一个新字符串。