【CSharp】LINQ

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获得该分组中的所有赛车手,用姓氏对它们进行排序,在根据姓名创建一个新字符串。

 

 

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhy29563

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值