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

posted @ 2012-03-21 23:35  Xiao Tian  阅读(321)  评论(0编辑  收藏  举报