如何正确看待Linq的DistinctBy扩展和ForEach扩展
在微软标准的Linq中,并没有DistinctBy扩展和ForEach扩展,但在平时使用工作中却又经常需要使用到这两个功能,照理来说,微软在Linq中应该包含这两个扩展才对,可事实上为什么并没有呢?本文我就来说说自己对这两个扩展的理解!
关于DistinctBy扩展
顾名思义,DistinctBy扩展就是根据一个键值进行唯一性的筛选,将有重复键值的元素剔除,仅保留一个!当然Linq中有Distinct扩展,但其功能简直是弱爆了!用过的同志相信都对Distinct扩展吐槽无数遍了吧!如果你要使用Distinct扩展实现DistinctBy扩展的功能,还要专门定义一个辅助类!Oh,My God!这简直是对程序员的谋杀!但是等经历过无数个纠结之后,我突然发现,微软在标准的Linq中实际上是包含了DistinctBy扩展的功能的,只不过不那么直接,看下面的示例代码:
1 class Person 2 { 3 public int Age { get; set; } 4 public string Name { get; set; } 5 public override string ToString() 6 { 7 return string.Format("Age:{0} Name:{1}", Age, Name); 8 } 9 } 10 [STAThread] 11 static void Main() 12 { 13 var persons = new[] { 14 new Person{Age = 10,Name="a"}, 15 new Person{Age = 10,Name="b"}, 16 new Person{Age = 10,Name="c"}, 17 new Person{Age = 20,Name="d"} 18 }; 19 foreach (var p in persons.GroupBy(o=>o.Age).Select(g=>g.First())) 20 { 21 Console.WriteLine(p); 22 } 23 }
运行结果
从结果看,代码成功完成了根据键值Age进行Distinct的操作。
明眼人可能一眼就看出来了,不错,就是使用GroupBy、Select和First这三个子操作组合出DistinctBy的功能!只不过实现一个DistinctBy功能需要写这么做代码实在是不好,于是可以封装一下,把GroupBy、Select和First这三个子操作的组合封装成一个DistinctBy扩展方法,就可以方便的使用了。封装的代码 too simple,就不贴上来了!
关于ForEach扩展
为什么微软不在Linq中添加ForEach扩展,这个问题的讨论在网上已经很多了,总结一下主要是有下面几点原因:
1.Linq中已经有Select扩展了,因此不必在实现一个ForEach,因为在大多数情况下可以用Select来实现ForEach的功能(当然我不建议这样做,因为Select是延迟操作,如果仅仅实现ForEach功能,有些时候代码不能够按照设计意图正确运行);
2.ForEach破坏了Linq的编程模式,就是说破坏了链式编程模式,所谓链式编程,大概的意思就是将多个操作通过点号"."链接在一起,相信写过Linq的同志很清楚这句话的含义吧!
我看到网上有人这么来设计ForEach扩展:
1 public static void ForEach<T>(this IEnumerable<T> source, Action<T> foreachAction) 2 { 3 foreach (var t in source) 4 { 5 foreachAction(t); 6 } 7 }
我不得不说这是简直是对Linq的“亵渎”啊,试想,如果别人使用了你的这个ForEach之后,后续操作岂不是成天方夜谭了吗?所以说这样设计ForEach是万万不可的!即使勉强要设计,也要设计成这样才对啊!
1 public static IEnumerable<T> ForEach<T>(this IEnumerable<T> source, Action<T> foreachAction) 2 { 3 foreach (var t in source) 4 { 5 foreachAction(t); 6 } 7 return source; 8 }
而且在我看来,这样添加ForEach扩展并不妥当,原因在于:这样设计的ForEach扩展,破坏了Linq的延迟操作特性!
所以说,我认为微软不在Linq中包含ForEach扩展算是一个正确的选择,如果要用到ForEach功能,还是老老实实用foreach语句来写吧!其实也并不复杂!