LINQ之延迟执行标准查询操作符(下)
操作符:Join
描述:基于匹配键对2个序列的元素进行关联,Join保留了TOuter中元素的顺序,并对于这些元素中的每一个元素,保留TInner中匹配元素的顺序.(来自msdn)
原型:2种
原型一:
public static IEnumerable<TResult> Join<TOuter, TInner, TKey, TResult>( this IEnumerable<TOuter> outer, IEnumerable<TInner> inner, Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector, Func<TOuter, TInner, TResult> resultSelector )
Join的参数比较多,需要特别注意。举个例子:
List<Category> categories = new List<Category> { new Category{Id = 1,Name = "Food"}, new Category{Id = 2,Name = "Toys"}, new Category{Id = 3,Name = "Fruit"} }; List<Product> products = new List<Product> { new Product{ProductId = 201,ProductName = "Chuckit",CategoryId = 2}, new Product{ProductId = 202,ProductName = "SafeMade",CategoryId = 2}, new Product{ProductId = 101,ProductName = "Taste",CategoryId = 1}, new Product{ProductId = 102,ProductName = "Canidae",CategoryId = 1}, new Product{ProductId = 103,ProductName = "Flavor",CategoryId = 1} }; var productList = categories.Join(products, c => c.Id, p => p.CategoryId, (c, p) => new { c.Name, p.ProductName }); foreach (var product in productList) { Console.WriteLine("{0}----{1}", product.Name, product.ProductName); }
在这个例子中,我们将categories和products通过categories.Id和products.CategoryId进行关联,返回一个新的对象的序列。
原型二略。
操作符:GroupJoin
描述:基于键相等对两个序列的元素进行关联并对结果进行分组.(来自msdn)
原型:
public static IEnumerable<TResult> GroupJoin<TOuter, TInner, TKey, TResult>( this IEnumerable<TOuter> outer, IEnumerable<TInner> inner, Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector, Func<TOuter, IEnumerable<TInner>, TResult> resultSelector )
GroupJoin和Join很类似,GroupJoin会对返回的结果给予TOuter的Key进行分组。
对于返回的结果,TOuter中的一个元素可能对应多个TInner元素。
注意:如果TInner中没有TOuter的给定元素的关联元素,则该元素的匹配序列为空,但仍将出现在结果中.
举个例子:
List<Category> categories = new List<Category> { new Category{Id = 1,Name = "Food"}, new Category{Id = 2,Name = "Toys"}, new Category{Id = 3,Name = "Fruit"} }; List<Product> products = new List<Product> { new Product{ProductId = 201,ProductName = "Chuckit",CategoryId = 2}, new Product{ProductId = 202,ProductName = "SafeMade",CategoryId = 2}, new Product{ProductId = 101,ProductName = "Taste",CategoryId = 1}, new Product{ProductId = 102,ProductName = "Canidae",CategoryId = 1}, new Product{ProductId = 103,ProductName = "Flavor",CategoryId = 1} }; var productList = categories.GroupJoin(products, c => c.Id, p => p.CategoryId, (c, ps) => new { c.Name,Products= ps.Select(p=>p.ProductName) }); foreach (var product in productList) { Console.WriteLine(product.Name+":"); foreach (var item in product.Products) { Console.WriteLine(item); } }
打印出来的结果是:
Food:
Taste
Canidae
Flavor
Toys:
Chuckit
SafeMade
Fruit:
我们可以发现Fruit这个category没有匹配的产品,但是也被打印出来了。
操作符:GroupBy
描述:GroupBy主要用来对输入序列进行分组,并且返回IGrouping<K, T>
原型:GroupBy提供了众多的原型,我们主要关注4个:
原型一:
public static IEnumerable<IGrouping<TKey, TSource>> GroupBy<TSource, TKey>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector )
List<Product> products = new List<Product> { new Product{ProductId = 201,ProductName = "Chuckit",CategoryId = 2}, new Product{ProductId = 202,ProductName = "SafeMade",CategoryId = 2}, new Product{ProductId = 101,ProductName = "Taste",CategoryId = 1}, new Product{ProductId = 102,ProductName = "Canidae",CategoryId = 1}, new Product{ProductId = 103,ProductName = "Flavor",CategoryId = 1} }; var productList = products.GroupBy(p => p.CategoryId).OrderBy(item=>item.Key); foreach (var keyGroupProducts in productList) { Console.WriteLine(keyGroupProducts.Key + ":"); foreach (var product in keyGroupProducts) { Console.WriteLine(product.ProductName); } }
输出结果:
1:
Taste
Canidae
Flavor
2:
Chuckit
SafeMade
原型一比较简单,主要包含2个参数,一个输入序列,另外一个是selector。
在这个例子中,我们通过对products序列进行按categoryid进行分组,然后按照分组后的key进行排序。
原型二:
public static IEnumerable<IGrouping<TKey, TSource>> GroupBy<TSource, TKey>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer )
原型二为我们多提供了IEqualityComparer参数用以自定义比较方法
public interface IEqualityComparer<in T>
这个接口提供了2个方法bool Equals(T x, T y)和int GetHashCode(T obj)2个方法
注意IEqualityComparer是根据Key来比较的,例如,我们可以写个comparer比较器:
public class ProductEqualityComparer : IEqualityComparer<int> { public bool Equals(int x, int y) { if (x == 0) return false; if (y == 0) return false; return IsSameVendor(x) == IsSameVendor(y); } public int GetHashCode(int obj) { int start = 1; int end = 100; return IsSameVendor(obj) ? start.GetHashCode() : end.GetHashCode(); } public bool IsSameVendor(int id) { return id < 100; } }
ProductEqualityComparer comparer = new ProductEqualityComparer(); List<Product> products = new List<Product> { new Product{ProductId = 201,ProductName = "Chuckit",CategoryId = 2}, new Product{ProductId = 202,ProductName = "SafeMade",CategoryId = 200}, new Product{ProductId = 101,ProductName = "Taste",CategoryId = 1}, new Product{ProductId = 102,ProductName = "Canidae",CategoryId = 1}, new Product{ProductId = 103,ProductName = "Flavor",CategoryId = 1} }; IEnumerable<IGrouping<int, Product>> productList = products.GroupBy(p => p.CategoryId, comparer); foreach (var keyGroupProducts in productList) { Console.WriteLine(keyGroupProducts.Key + ":"+(comparer.IsSameVendor(keyGroupProducts.Key)?"Same Vendor":"Diff Vendor")); foreach (var product in keyGroupProducts) { Console.WriteLine(product.ProductName); } }
原型三:
public static IEnumerable<IGrouping<TKey, TElement>> GroupBy<TSource, TKey, TElement>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector )
与原型一不一样的是,原型一返回了与输入序列同样的类型元素,原型三可以根据自己的需求,返回所需的元素类型,我们来看个例子:
List<Product> products = new List<Product> { new Product{ProductId = 201,ProductName = "Chuckit",CategoryId = 2}, new Product{ProductId = 202,ProductName = "SafeMade",CategoryId = 2}, new Product{ProductId = 101,ProductName = "Taste",CategoryId = 1}, new Product{ProductId = 102,ProductName = "Canidae",CategoryId = 1}, new Product{ProductId = 103,ProductName = "Flavor",CategoryId = 1} }; var productList = products.GroupBy(p => p.CategoryId, p=>new{p.ProductId,p.ProductName}).OrderBy(p=>p.Key); foreach (var keyGroupProducts in productList) { Console.WriteLine("Category: "+keyGroupProducts.Key); foreach (var product in keyGroupProducts) { Console.WriteLine("{0}-{1}",product.ProductId, product.ProductName); } }
我们返回的不再是Product,而是我们新建的一个匿名类型,包含ProductId和ProductName
原型四:
public static IEnumerable<IGrouping<TKey, TElement>> GroupBy<TSource, TKey, TElement>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, IEqualityComparer<TKey> comparer )
原型四实际上是原型二和原型三的结合,比较简单,就不举例子了。
操作符:Distinct
描述:Distinct操作符主要用于过滤重复的元素,2个元素相等是通过GetHashCode和Equals来判断的,原型二为我们提供了自己写对等条件的方法
原型:2种
原型一:
public static IEnumerable<TSource> Distinct<TSource>( this IEnumerable<TSource> source )
这个原型很简单,我们借用msdn的例子来说明:
List<int> ages = new List<int> { 21, 46, 46, 55, 17, 21, 55, 55 }; IEnumerable<int> distinctAges = ages.Distinct(); Console.WriteLine("Distinct ages:"); foreach (int age in distinctAges) { Console.WriteLine(age); }
输出结果:
Distinct ages:
21
46
55
17
重复的46,55被过滤掉了。
原型二:
public static IEnumerable<TSource> Distinct<TSource>( this IEnumerable<TSource> source, IEqualityComparer<TSource> comparer )
我们先定义一个相等比较器:
public class ProductEqualityComparer : IEqualityComparer<Product> { public bool Equals(Product x, Product y) { if (x == null||y==null) return false; return x.ProductId == y.ProductId && x.CategoryId == y.CategoryId; } public int GetHashCode(Product obj) { int productid = obj.ProductId; int categoryid = obj.CategoryId; return productid.GetHashCode() + categoryid.GetHashCode(); } }
然后,在查询的时候,应用这个比较器:
ProductEqualityComparer comparer = new ProductEqualityComparer(); List<Product> products = new List<Product> { new Product{ProductId = 201,ProductName = "Chuckit",CategoryId = 2}, new Product{ProductId = 201,ProductName = "CHUCKIT",CategoryId = 2}, new Product{ProductId = 101,ProductName = "Taste",CategoryId = 1}, new Product{ProductId = 102,ProductName = "Canidae",CategoryId = 1}, new Product{ProductId = 103,ProductName = "Flavor",CategoryId = 1} }; var productList = products.Distinct(comparer); foreach (var product in productList) { Console.WriteLine("{0}:{1}:{2}",product.CategoryId,product.ProductId,product.ProductName); }
输出结果为:
2:201:Chuckit
1:101:Taste
1:102:Canidae
1:103:Flavor
可以发现,201这个product第二个ProductName为全大写的被过滤掉了,因为根据我们的相等比较器,两个201product是一样的。
操作符:Union
描述:连接2个序列,相同的元素只会保留一个
原型:2种
原型一:
public static IEnumerable<TSource> Union<TSource>( this IEnumerable<TSource> first, IEnumerable<TSource> second )
这也是个很简单的操作符,我们还是来看个简单的例子:
List<int> int1 = new List<int>{12,42,3,2}; List<int> int2 = new List<int> {24, 3 }; var unitInts = int1.Union(int2); foreach (var unitInt in unitInts) { Console.WriteLine(unitInt); }
输出结果为:
12
42
3
2
24
int2的3被过滤掉了,因为int1已经存在3这个元素了
原型二为我们提供了相等比较器:
public static IEnumerable<TSource> Union<TSource>( this IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer )
跟上面的Distinct的原型二类似,我们就不再举例了。
操作符:Intersect
描述:返回2个序列的交集
原型:2种
题外话:看到这里,其实我们就很容易猜出这2种原型是什么了,无非一种是采用默认对等比较的交集操作,另一种是可以自己提供相等比较器的方法
原型一:
public static IEnumerable<TSource> Intersect<TSource>( this IEnumerable<TSource> first, IEnumerable<TSource> second )
原型二:
public static IEnumerable<TSource> Intersect<TSource>( this IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer )
我们举个原型一的例子吧:
List<int> int1 = new List<int> { 12, 42, 3, 2 }; List<int> int2 = new List<int> { 24, 3 }; var unitInts = int1.Intersect(int2); foreach (var unitInt in unitInts) { Console.WriteLine(unitInt); }
结果集只会返回3。
操作符:Except
描述:该操作符返回存在于序列1但是不存在序列2的所有元素
原型:2种
原型一:
public static IEnumerable<TSource> Except<TSource>( this IEnumerable<TSource> first, IEnumerable<TSource> second )
原型二:
public static IEnumerable<TSource> Except<TSource>( this IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer )
我们举个原型一的例子:
List<int> int1 = new List<int> { 12, 42, 3, 2}; List<int> int2 = new List<int> { 24, 3 }; var unitInts = int1.Except(int2); foreach (var unitInt in unitInts) { Console.WriteLine(unitInt); }
返回的结果集为:
12
42
2
元素3在int2中,所以被过滤掉了,int2中的元素24,不在int1中,所以不会被返回回来。
操作符:Cast
描述:将非泛型的结果皆转成泛型结果集,以支持大部分的标准查询操作符。
如果我们有一个序列是在.net 2.0框架上做的,但是,我们又希望用LINQ来操作,此时,我们就可以先将该序列Cast操作后转成IEnumerable<T>.
原型:
public static IEnumerable<TResult> Cast<TResult>( this IEnumerable source )
借用msdn的例子来说明:
System.Collections.ArrayList fruits = new System.Collections.ArrayList(); fruits.Add("apple"); fruits.Add("mango"); IEnumerable<string> query = fruits.Cast<string>().Select(fruit => fruit); foreach (string fruit in query) { Console.WriteLine(fruit); }
操作符:OfType
描述:将可以转成某种类型的元素作为结果集输出来,跟Cast一样,OfType的输入序列也是非泛型的IEnumerable
原型:
public static IEnumerable<TResult> OfType<TResult>( this IEnumerable source )
还是借用msdn的例子:
System.Collections.ArrayList fruits = new System.Collections.ArrayList(4); fruits.Add("Mango"); fruits.Add("Orange"); fruits.Add("Apple"); fruits.Add(3.0); fruits.Add("Banana"); // Apply OfType() to the ArrayList. IEnumerable<string> query1 = fruits.OfType<string>(); Console.WriteLine("Elements of type 'string' are:"); foreach (string fruit in query1) { Console.WriteLine(fruit); }
ArrayList的签名为:
public class ArrayList : IList, ICollection, IEnumerable, ICloneable
它是没有实现IEnumerable<T>,经过OfType后,返回的结果集就支持IEnumerable了。
操作符:AsEnumerable
描述:输入一个IEnumerable<T>序列,返回一个IEnumerable<T>序列。
看到这个描述也许你会觉得很好玩:这不是多此一举吗,没有任何操作,只是为了返回跟输入序列一样的序列。
在稍后的LINQ TO SQL我们再来详细讨论这个问题。
原型:
public static IEnumerable<TSource> AsEnumerable<TSource>( this IEnumerable<TSource> source )
操作符:DefaultIfEmpty
原型一:
public static IEnumerable<TSource> DefaultIfEmpty<TSource>( this IEnumerable<TSource> source )
原型二:
public static IEnumerable<TSource> DefaultIfEmpty<TSource>( this IEnumerable<TSource> source, TSource defaultValue )
描述:该操作符主要用来描述,如果序列中的元素为空,该返回什么样的元素。原型一采用默认的对象的构造函数来创建一个空的对象,原型二允许我们定义一个默认的元素。
借用msdn的例子来说明原型二:
class Pet { public string Name { get; set; } public int Age { get; set; } } public static void DefaultIfEmptyEx2() { Pet defaultPet = new Pet { Name = "Default Pet", Age = 0 }; List<Pet> pets1 = new List<Pet>{ new Pet { Name="Barley", Age=8 }, new Pet { Name="Boots", Age=4 }, new Pet { Name="Whiskers", Age=1 } }; foreach (Pet pet in pets1.DefaultIfEmpty(defaultPet)) { Console.WriteLine("Name: {0}", pet.Name); } List<Pet> pets2 = new List<Pet>(); foreach (Pet pet in pets2.DefaultIfEmpty(defaultPet)) { Console.WriteLine("\nName: {0}", pet.Name); } }
输出结果为:
Name: Barley Name: Boots Name: Whiskers Name: Default Pet
第二个序列为空,因此输出了我们事先定义的默认的对象.
操作符:Range
原型:
public static IEnumerable<int> Range( int start, int count )
该操作符返回输入序列的一部分元素
操作符:Repeat
原型:
public static IEnumerable<TResult> Repeat<TResult>( TResult element, int count )
对元素element循环count次数
操作符:Empty
原型:
public static IEnumerable<TResult> Empty<TResult>()
描述:这是一个静态方法,但不是一个扩展方法。
IEnumerable<string> strings = Enumerable.Empty<string>(); foreach(string s in strings) Console.WriteLine(s); Console.WriteLine(strings.Count());
输出结果为:
0