C# 当中 LINQ 的常规用法 (Lambda 方式)

.1. IEnuemrable<T>.Select()

Select 方法比较简单,就是在原有序列的基础上,为每个元素建立一个新的输出形式(类型)。

标准用法如下:

public class TestClass
{
	public string Name { get; set; }

	public int Age { get; set; }
}

void Main()
{
	var testList = new List<TestClass>
	{
		new TestClass{Name = "张三",Age = 18},
		new TestClass{Name="李四",Age = 32},
		new TestClass{Name="王二",Age = 24}
	};

	var selectResult = testList.Select(student => new
	{
		Name = student.Name,
		NewAge = student.Age + 20
	});

	foreach (var student in selectResult)
	{
		Console.WriteLine($"姓名:{student.Name},新的年龄:{student.NewAge}");
	}
}

输出结果:

姓名:张三,新的年龄:38
姓名:李四,新的年龄:52
姓名:王二,新的年龄:44

这样 newResult 的结果就是我们所投射出来新序列,同时 IEnumerbale<T>.Select() 也拥有 延迟执行 的特性,只会在我们需要用到的时候才会进行计算。

2. IEnumerable<T>.SelectMany()

SelectMany() 方法的作用则与 Select() 方法不同,SelectMany() 是用于将每个元素的子集合合并为一个新的集合。

标准用法如下:

void Main()
{
	var demoList = new List<Demo>()
	{
		new Demo(){Names = new List<string>{"a","b","c","d"}},
        new Demo(){Names = new List<string>{"e","f","g","h"}},
        new Demo(){Names = new List<string>{"i","j","k","l"}},
        new Demo(){Names = new List<string>{"m","n","o","p"}},
	};
	
	var selectResult = demoList.Select(item=>item.Names);
	Console.WriteLine("Select 操作的结果...");
	foreach(var selectItem in selectResult)
	{
		foreach(var value in selectItem)
		{
			Console.WriteLine($"Value:{value}");
		}
	}
	
	Console.WriteLine("================================");
	Console.WriteLine("SelectMany 操作的结果...");
	
	var selectManyResult = demoList.SelectMany(item=>item.Names);
	foreach(var selectManyItem in selectManyResult)
	{
		Console.WriteLine($"Value:{selectManyItem}");
	}
}

public class Demo
{
	public List<string> Names { get; set; }
}

在本例当中这两个方法分别输出的是 IEnumerable<List<string>>IEnumerable<string> ,这里就可以看出来 SelectionMany() 方法将子集合扁平化输出成一个结果集。

输出结果:

Select 操作的结果...
Value:a
Value:b
Value:c
Value:d
Value:e
Value:f
Value:g
Value:h
Value:i
Value:j
Value:k
Value:l
Value:m
Value:n
Value:o
Value:p
================================
SelectMany 操作的结果...
Value:a
Value:b
Value:c
Value:d
Value:e
Value:f
Value:g
Value:h
Value:i
Value:j
Value:k
Value:l
Value:m
Value:n
Value:o
Value:p

IEnumerable<T>.SelectMany() 还拥有另外一个重载方法,这个新的重载方法多了一个 resultSelector 参数。在这个委托当中,可以传入 TSourceTCollection 让我们将主表的数据与从表进行合并。

标准用法如下:

void Main()
{
	Store[] stores = new Store[]
	{
		new Store()
		{
			Name = "App Store",
			Products = new string[] {"iPhone 8", "iPhone 8s", "iPhone X"}
		},
		new Store()
		{
			Name = "Google Store",
			Products = new string[] {"Pixel", "Pixel 2"}
		}
	};

	var result = stores.SelectMany(store  => store.Products, (store, product) => new 
	{
		StoreName = store.Name,
		ProductName = product
	});
	
	foreach(var item in result)
	{
		Console.WriteLine($"商店名称:{item.StoreName},产品名称:{item.ProductName}");
	}
}

class Store
{
	public string Name { get; set; }
	public string[] Products { get; set; }
}

输出结果:

商店名称:App Store,产品名称:iPhone 8
商店名称:App Store,产品名称:iPhone 8s
商店名称:App Store,产品名称:iPhone X
商店名称:Google Store,产品名称:Pixel
商店名称:Google Store,产品名称:Pixel 2

3. IEnuemrable<T>.Where()

IEnumerable<T>.Where(Func<T,bool>) 主要用于过滤序列当中需要的元素,与 Select() 一样也是拥有 延迟执行 的特性。

标准用法:

void Main()
{
	var integers = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	var result = integers.Where(x => x >= 5);

	foreach (var @int in result)
	{
		Console.WriteLine($"整形数据:{@int}");
	}
}

输出结果:

整形数据:5
整形数据:6
整形数据:7
整形数据:8
整形数据:9

4. IEnuemrable<T>.OrderBy() 与 IEnuemrable<T>.OrderByDescending()

上述两个方法主要用于针对序列进行排序操作,OrderBy() 方法是升序排列,而 OrderByDescending() 则是降序排列。

标准用法:

void Main()
{
	var integers = new[] { 3, 1, 2, 8, 5, 6, 7, 4, 9 };
	var orderByResult = integers.OrderBy(i=>i);

	Console.WriteLine("升序排列结果.");
	foreach (var @int in orderByResult)
	{
		Console.WriteLine($"整形数据:{@int}");
	}
	
	Console.WriteLine("降序排列结果.");
	var orderByDescResult = integers.OrderByDescending(i => i);
	foreach (var @int in orderByDescResult)
	{
		Console.WriteLine($"整形数据:{@int}");
	}
}

输出结果:

升序排列结果.
整形数据:1
整形数据:2
整形数据:3
整形数据:4
整形数据:5
整形数据:6
整形数据:7
整形数据:8
整形数据:9
降序排列结果.
整形数据:9
整形数据:8
整形数据:7
整形数据:6
整形数据:5
整形数据:4
整形数据:3
整形数据:2
整形数据:1

除了上述的基本排序以外,有的时候我们可能会有几个条件一起进行排序操作,例如先按照年龄排序,之后再按照成绩排序。这个时候就可以使用到 IEnumerable<T>.ThenBy()IEnumerable<T>.ThenByDescending() 来继续进行排序。

标准用法:

public class Student
{
	public string Name { get; set; }

	public int Age { get; set; }

	public int Score { get; set; }
}

void Main()
{
	// 初始化数据
	var students = new List<Student>
	{
		new Student{Name="张三",Age=14,Score=120},
		new Student{Name="李四",Age=17,Score=80},
		new Student{Name="王二",Age=11,Score=170},
		new Student{Name ="孙五",Age=21,Score=145}
	};

	// 首先按照成绩降序排序,然后按照年龄升序排序
	Console.WriteLine("使用 Then XX 方法进行排序。");
	var result = students.OrderByDescending(student=>student.Score).ThenBy(student=>student.Age);
	// 输出排序结果
	Output(result);
	
	// 如果不使用 ThenXX 进行排序,而直接使用 OrderXX 进行多个排序条件排序结果
	Console.WriteLine("没有使用 Then XX 方法进行排序。");
	var newResult = students.OrderByDescending(student => student.Score).OrderBy(student => student.Age);
	Output(newResult);
	
	void Output(IEnumerable<Student> outputStudents)
	{
		foreach (var student in outputStudents)
		{
			Console.WriteLine($"学生名称:{student.Name},学生年龄:{student.Age},学生成绩:{student.Score}");
		}
	}
}

输出结果:

使用 Then XX 方法进行排序。
学生名称:王二,学生年龄:11,学生成绩:170
学生名称:孙五,学生年龄:21,学生成绩:145
学生名称:张三,学生年龄:14,学生成绩:120
学生名称:李四,学生年龄:17,学生成绩:80
没有使用 Then XX 方法进行排序。
学生名称:王二,学生年龄:11,学生成绩:170
学生名称:张三,学生年龄:14,学生成绩:120
学生名称:李四,学生年龄:17,学生成绩:80
学生名称:孙五,学生年龄:21,学生成绩:145

可以看到如果没有使用 ThenBy() 方法排序的结果,第一个条件根本没有起作用。所以在使用 LINQ 对序列进行排序的时候应该注意这一点,否则得到的结果可能与预期的不一样。

5. IEnuemrable<T>.GroupBy()

IEnumerable<T>.GroupBy() 方法主要用于将序列按照指定的列进行分组,这样我们就可以很方便对这些结果进行处理。

IEnumerable<T>.GroupBy() 方法一样是拥有八种不同参数的重载,但按照工作方式来说只有四类,只是每一类都会有一个支持 comparer 比较器的重载。

GroupBy() 方法拥有 延迟执行 特性。

下面的例子列举了不同重载的使用方法:

方法 1

public static IEnumerable<IGrouping<TKey, TSource>> GroupBy<TSource, TKey>(
    this IEnumerable<TSource> source,
    Func<TSource, TKey> keySelector);

keySelector 指定的是分组所需要的的列/属性,最后的结果会是一个分组结果的序列。这个序列的值是使用 TKeyTSource 组成的分组结果,TKey 指代的是用作分组的列/属性,而 TSource 也是一个序列,存储的是这个分组下的结果。

标准用法:

public class Student
{
    public string Name { get; set; }

    public bool Graduation { get; set; }

    public int Age { get; set; }

    public int Score { get; set; }

    public string City { get; set; }

    public override string ToString()
    {
        return $"姓名:{Name},年龄:{Age},分数:{Score},是否毕业:{Graduation},城市:{City}";
    }
}

class Program
{
    static void Main(string[] args)
    {
        var students = new List<Student>
        {
            new Student{Name = "张三",Age = 15,Score = 94,Graduation = true,City = "北京"},
            new Student{Name = "李四",Age = 17,Score = 47,Graduation = false,City = "北京"},
            new Student{Name = "王二",Age = 19,Score = 77,Graduation = false,City = "广州"},
            new Student{Name = "孙五",Age = 14,Score = 14,Graduation = false,City = "上海"}
        };

        var groupByResult = students.GroupBy(x => x.City);
        foreach (var item in groupByResult)
        {
            Console.WriteLine($"分组城市:{item.Key}");
            foreach (var student in item)
            {
                Console.WriteLine(student.ToString());
            }
        }
    }
}

输出结果:

分组城市:北京
姓名:张三,年龄:15,分数:94,是否毕业:True,城市:北京
姓名:李四,年龄:17,分数:47,是否毕业:False,城市:北京
分组城市:广州
姓名:王二,年龄:19,分数:77,是否毕业:False,城市:广州
分组城市:上海
姓名:孙五,年龄:14,分数:14,是否毕业:False,城市:上海

方法 2

public static IEnumerable<IGrouping<TKey, TElement>> GroupBy<TSource, TKey, TElement>(
    this IEnumerable<TSource> source,
    Func<TSource, TKey> keySelector,
    Func<TSource, TElement> elementSelector);

其第一个参数的意思与之前一样,用于指定分组条件。第二个 elementSelector 参数则是与 Select() 方法类似,可以指定输出的分组结果类型。

标准用法:

public class Student
{
    public string Name { get; set; }

    public bool Graduation { get; set; }

    public int Age { get; set; }

    public int Score { get; set; }

    public string City { get; set; }

    public override string ToString()
    {
        return $"姓名:{Name},年龄:{Age},分数:{Score},是否毕业:{Graduation},城市:{City}";
    }
}

class Program
{
    static void Main(string[] args)
    {
        var students = new List<Student>
        {
            new Student{Name = "张三",Age = 15,Score = 94,Graduation = true,City = "北京"},
            new Student{Name = "李四",Age = 17,Score = 47,Graduation = false,City = "北京"},
            new Student{Name = "王二",Age = 19,Score = 77,Graduation = false,City = "广州"},
            new Student{Name = "孙五",Age = 14,Score = 14,Graduation = false,City = "上海"}
        };

        var groupByResult = students.GroupBy(x => x.City,student=>new{student.Name,student.City});
        foreach (var item in groupByResult)
        {
            Console.WriteLine($"分组城市:{item.Key}");
            foreach (var student in item)
            {
                Console.WriteLine($"姓名:{student.Name},城市:{student.City}");
            }
        }
    }
}

输出结果:

标准用法:

分组城市:北京
姓名:张三,城市:北京
姓名:李四,城市:北京
分组城市:广州
姓名:王二,城市:广州
分组城市:上海
姓名:孙五,城市:上海

方法 3

public static IEnumerable<TResult> GroupBy<TSource, TKey, TResult>(
    this IEnumerable<TSource> source,
    Func<TSource, TKey> keySelector,
    Func<TKey, IEnumerable<TSource>, TResult> resultSelector);

本方法重载与之前的方法不一样,没有了 elementSelector 参数,变成了 resultSelector 参数,并且其返回值也由 IEnumerable<IGrouping<TKey, TSource>> 变成了 IEnumerable<YResult>

在这个方法当中,我们只需要通过 resultSelector 委托即可指定需要输出的数据类型,这里该委托的 TKey 参数是分组的列/属性,而 IEnumerable<TSource> 则是每个分组结果的关联序列。

标准用法:

public class Student
{
    public string Name { get; set; }

    public bool Graduation { get; set; }

    public int Age { get; set; }

    public int Score { get; set; }

    public string City { get; set; }

    public override string ToString()
    {
        return $"姓名:{Name},年龄:{Age},分数:{Score},是否毕业:{Graduation},城市:{City}";
    }
}

class Program
{
    static void Main(string[] args)
    {
        var students = new List<Student>
        {
            new Student{Name = "张三",Age = 15,Score = 94,Graduation = true,City = "北京"},
            new Student{Name = "李四",Age = 17,Score = 47,Graduation = false,City = "北京"},
            new Student{Name = "王二",Age = 19,Score = 77,Graduation = false,City = "广州"},
            new Student{Name = "孙五",Age = 14,Score = 14,Graduation = false,City = "上海"}
        };

        var groupByResult = students.GroupBy(x => x.City, (key, enumerable) =>
        {
            return new
            {
                City = key,
                Avg = enumerable.Average(x => x.Score),
                Max = enumerable.Max(x => x.Score)
            };
        });

        foreach (var student in groupByResult)
        {
            Console.WriteLine($"城市:{student.City},平均分:{student.Avg},最高分:{student.Max}");
        }
    }
}

输出结果:

城市:北京,平均分:70.5,最高分:94
城市:广州,平均分:77,最高分:77
城市:上海,平均分:14,最高分:14

方法 4

public static IEnumerable<TResult> GroupBy<TSource, TKey, TElement, TResult>(
    this IEnumerable<TSource> source,
    Func<TSource, TKey> keySelector,
    Func<TSource, TElement> elementSelector,
    Func<TKey, IEnumerable<TElement>, TResult> resultSelector);

最后一个方法与之前的方法一样,不过多了 elementSelector,与第二个方法一样,用于指定要选择的属性字段,排除其他字段。

标准用法:

    public class Student
    {
        public string Name { get; set; }

        public bool Graduation { get; set; }

        public int Age { get; set; }

        public int Score { get; set; }

        public string City { get; set; }

        public override string ToString()
        {
            return $"姓名:{Name},年龄:{Age},分数:{Score},是否毕业:{Graduation},城市:{City}";
        }
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            var students = new List<Student>
            {
                new Student{Name = "张三",Age = 15,Score = 94,Graduation = true,City = "北京"},
                new Student{Name = "李四",Age = 17,Score = 47,Graduation = false,City = "北京"},
                new Student{Name = "王二",Age = 19,Score = 77,Graduation = false,City = "广州"},
                new Student{Name = "孙五",Age = 14,Score = 14,Graduation = false,City = "上海"}
            };

            var groupByResult = students.GroupBy(x => x.City, x=>new{x.Name,x.City,x.Age,x.Score},(key, enumerable) =>
            {
                return new
                {
                    City = key,
                    Avg = enumerable.Average(x => x.Score),
                    Max = enumerable.Max(x => x.Score),
                    AvgAge = enumerable.Average(x=>x.Age)
                };
            });

            foreach (var student in groupByResult)
            {
                Console.WriteLine($"城市:{student.City},平均分:{student.Avg},最高分:{student.Max},平均年龄:{student.AvgAge}");
            }
        }
    }

输出结果:

城市:北京,平均分:70.5,最高分:94,平均年龄:16
城市:广州,平均分:77,最高分:77,平均年龄:19
城市:上海,平均分:14,最高分:14,平均年龄:14

6. IEnuemrable<T>.Join()

IEnumerable<T>.Join() 方法一般用来连接两个不同的表进行关联查询,其方法定义大概如下。

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);

假设我们拥有两张表,分别是员工表与联系方式表,都是通过一个 Id 相互关联。如果我们需要找到某个员工的联系方式,那么员工是主表则为 inner,而联系方式是从表,则为 outer 。另外两个 Selector 分别用于指定各自的关联属性。

IEnumerable<T>.Join() 拥有 延迟执行 的特性,并且是内连查询。

标准用法:

public class Person
{
    public int Id { get; set; }

    public string Name { get; set; }
}

public class ContactInformation
{
    public int PersonId { get; set; }

    public string PhoneNumber { get; set; }

    public string Address { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        // 初始化员工信息
        var persons = new[] {new Person {Id = 1, Name = "张三"}, new Person {Id = 2, Name = "李四"}, new Person {Id = 3, Name = "王二"}};
        
        // 初始化员工的联系方式
        var contactInfos = new[]
        {
            new ContactInformation {PersonId = 1, Address = "上海", PhoneNumber = "001-00001"},
            new ContactInformation {PersonId = 2, Address = "北京", PhoneNumber = "002-00002"},
            new ContactInformation {PersonId = 3, Address = "广州", PhoneNumber = "003-00003"},
            new ContactInformation {PersonId = 1, Address = "深圳", PhoneNumber = "004-00004"}
        };

        var joinResult = contactInfos.Join(persons, contactInfo => contactInfo.PersonId, person => person.Id, (information, person) => new
        {
            Name = person.Name,
            PhoneNumber = information.PhoneNumber,
            Address = information.Address
        });

        foreach (var item in joinResult)
        {
            Console.WriteLine($"姓名:{item.Name},电话:{item.PhoneNumber},地址:{item.Address}");
        }
    }
}

输出结果:

姓名:张三,电话:001-00001,地址:上海
姓名:李四,电话:002-00002,地址:北京
姓名:王二,电话:003-00003,地址:广州
姓名:张三,电话:004-00004,地址:深圳

7. IEnuemrable<T>.GroupJoin()

以上一个 IEnumerable<T>.Join() 的方法结果为例,可以看到某个员工的联系方式数据有两笔甚至以上的时候就会拥有重复数据,这个时候就需要使用到 GroupJoin() 来进行处理。

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() 类似,都是根据 outer 的关联字段与 inner 的关联字段相等的数据进行查询,并支持进行汇总操作。

GroupJoin() 方法也具有 延迟执行 的特性,并且通过 SelectMany() 方法与 DefaultIfEmpty() 方法可以实现 Left Join 的查询效果。

标准用法:

public class Person
{
    public int Id { get; set; }

    public string Name { get; set; }
}

public class ContactInformation
{
    public int PersonId { get; set; }

    public string PhoneNumber { get; set; }

    public string Address { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        // 初始化员工信息
        var persons = new[] {new Person {Id = 1, Name = "张三"}, new Person {Id = 2, Name = "李四"}, new Person {Id = 3, Name = "王二"}};
        
        // 初始化员工的联系方式
        var contactInfos = new[]
        {
            new ContactInformation {PersonId = 1, Address = "上海", PhoneNumber = "001-00001"},
            new ContactInformation {PersonId = 2, Address = "北京", PhoneNumber = "002-00002"},
            new ContactInformation {PersonId = 3, Address = "广州", PhoneNumber = "003-00003"},
            new ContactInformation {PersonId = 1, Address = "深圳", PhoneNumber = "004-00004"}
        };

        var groupJoinResult = persons.GroupJoin(contactInfos, person => person.Id, contactInfo => contactInfo.PersonId, (person, infos) => new
        {
            Name = person.Name,
            Phones = string.Join(',',infos.Select(x=>x.PhoneNumber))
        });

        foreach (var item in groupJoinResult)
        {
            Console.WriteLine($"姓名:{item.Name},电话:{item.Phones}");
        }
    }
}

输出结果:

姓名:张三,电话:001-00001,004-00004
姓名:李四,电话:002-00002
姓名:王二,电话:003-00003

扩展写法,使用 SelectMany()DefaultIfEmpty() 实现左连接查询。

public class Person
{
    public int Id { get; set; }

    public string Name { get; set; }
}

public class ContactInformation
{
    public int PersonId { get; set; }

    public string PhoneNumber { get; set; }

    public string Address { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        // 初始化员工信息
        var persons = new[] {new Person {Id = 1, Name = "张三"}, new Person {Id = 2, Name = "李四"}, new Person {Id = 3, Name = "王二"}};
        
        // 初始化员工的联系方式
        var contactInfos = new[]
        {
            new ContactInformation {PersonId = 1, Address = "上海", PhoneNumber = "001-00001"},
            new ContactInformation {PersonId = 2, Address = "北京", PhoneNumber = "002-00002"},
            // ContactInformation {PersonId = 3, Address = "广州", PhoneNumber = "003-00003"},
            new ContactInformation {PersonId = 1, Address = "深圳", PhoneNumber = "004-00004"}
        };

        var groupJoinResult = persons.GroupJoin(contactInfos, person => person.Id, contactInfo => contactInfo.PersonId, (person, infos) => new
        {
            Name = person.Name,
            ContactInfos = infos
        }).SelectMany(x=>x.ContactInfos.DefaultIfEmpty(), (_, __) => new
        {
            PersonName = _.Name,
            PhoneNumber = __?.PhoneNumber ?? null
        });

        foreach (var item in groupJoinResult)
        {
            Console.WriteLine($"姓名:{item.PersonName},电话:{item.PhoneNumber}");
        }
    }
}

输出结果:

姓名:张三,电话:001-00001
姓名:张三,电话:004-00004
姓名:李四,电话:002-00002
姓名:王二,电话:

8. IEnuemrable<T>.Skip()

IEnumerable<T>.Skip(int N) 方法的主要作用就是从序列首部跳过序列中 N 个元素,然后返回其结果,这个方法还有两个衍生的方法。

第一个是 IEnumerable<T>.SkipLast(int N) 方法,与 Skip(int N) 方法相反,它会从序列的尾部跳过 N 个元素再返回结果。

第二个则是 IEnumerbale<T>.SkipWhile(Func<T,bool> predicate) ,它可以传入一个过滤条件,当序列当中遇到第一个不满足条件的元素时,就会返回该元素及其后续的所有元素,而略过之前符合条件的元素。

标准用法:

class Program
{
	static void Main()
	{
		void OutputResult(IEnumerable<int> ienumerable)
		{
			foreach (var item in ienumerable)
			{
				Console.WriteLine($"{item}");
			}
		}

		var integers = new List<int> {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

		// Skip() 方法演示。
		Console.WriteLine("Skip() 方法演示:");
		var skipResult = integers.Skip(5);
		OutputResult(skipResult);

		// SkipLast() 方法演示。
		Console.WriteLine("SkipLast() 方法演示:");
		var skipLastResult = integers.SkipLast(5);
		OutputResult(skipLastResult);

		// SkipWhile() 方法演示。
		Console.WriteLine("SkipWhile() 方法演示:");
		var skipWhileResult = integers.SkipWhile(@int => @int != 3);
		OutputResult(skipWhileResult);
	}
}

输出结果:

Skip() 方法演示:
6
7
8
9
10
SkipLast() 方法演示:
1
2
3
4
5
SkipWhile() 方法演示:
3
4
5
6
7
8
9
10

9. IEnuemrable<T>.Take()

IEnumerable<T>.Take(int N) 方法的作用是从序列首部选取 N 个元素返回结果。

它也拥有两个衍生的方法,第一个是 IEnumerable<T>.TakeLast(int N) 方法,与 Skip(int N) 方法相同。这个方法主要是从序列的尾部选取 N 个元素组成一个新的序列并返回其结果。

第二个方法也是相似,叫做 IEnumerable<T>.TakeWhile(Func<T,bool>) ,与 IEnumerable<T>.Skip(Func<T,bool>) 方法相反,本方法是会从序列首部开始获取元素,直到条件满足时就会停止获取,然后将这些结果返回成为一个新的序列。

标准用法:

class Program
{
	static void Main()
	{
		void OutputResult(IEnumerable<int> ienumerable)
		{
			foreach (var item in ienumerable)
			{
				Console.WriteLine($"{item}");
			}
		}

		var integers = new List<int> {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

		// Take() 方法演示。
		Console.WriteLine("Take() 方法演示:");
		var skipResult = integers.Take(5);
		OutputResult(skipResult);

		// TakeLast() 方法演示。
		Console.WriteLine("TakeLast() 方法演示:");
		var skipLastResult = integers.Take(3);
		OutputResult(skipLastResult);

		// TakeWhile() 方法演示。
		Console.WriteLine("TakeWhile() 方法演示:");
		var skipWhileResult = integers.TakeWhile(@int => @int != 3);
		OutputResult(skipWhileResult);
	}
}

输出结果:

Take() 方法演示:
1
2
3
4
5
TakeLast() 方法演示:
1
2
3
TakeWhile() 方法演示:
1
2

10. IEnuemrable<T>.Aggregate()

IEnumerable<T>.Aggregate() 方法的主要作用是帮助开发人员对一个序列的资料进行汇总处理,即会执行 N 次,每次会将之前的汇总结果与当前元素的值传入到一个 Func<TSource, TSource, TSource> 委托当中,其返回值就会作为新的汇总结果传递给下一个元素。

IEnumerable<T>.Aggregate() 共有 3 个重载方法,其运行机制与上述说明一致,只是在于初始化操作和返回值的时候不太一致。

public static TSource Aggregate<TSource>(
    this IEnumerable<TSource> source,
    Func<TSource, TSource, TSource> func);

其中 func 参数是每次执行汇总操作时的逻辑方法,第一个参数则是之前的汇总结果,第二个参数即是当前元素的值,最后一个返回值则是汇总完成的结果,将会作为下一次汇总操作时的传入参数。(即第一个参数的值)

public static TAccumulate Aggregate<TSource, TAccumulate>(
    this IEnumerable<TSource> source,
    TAccumulate seed,
    Func<TAccumulate, TSource, TAccumulate> func);

其中 seed 是汇总结果的初始值,这个值将会在第一次计算汇总结果的时候用到。

public static TResult Aggregate<TSource, TAccumulate, TResult>(
    this IEnumerable<TSource> source,
    TAccumulate seed,
    Func<TAccumulate, TSource, TAccumulate> func,
    Func<TAccumulate, TResult> resultSelector);

本方法与上一个方法类似,不同的是有一个 resultSelector 委托可以方便我们将数据重新投射到不同的类型当中去。

标准用法:

class Program
{
    void OutputResult(IEnumerable<int> ienumerable)
    {
        foreach (var item in ienumerable)
        {
            Console.WriteLine($"{item}");
        }
    }
    
    static void Main(string[] args)
    {
        var integers = new[] {3, 2, 1, 7, 10, 6, 4, 8, 9, 5};

        // IEnumerable<T>.Sum(x=>x) 效果,计算整个序列元素之和。
        var sumResult = integers.Aggregate((totalCount, nextItem) => totalCount + nextItem);
        Console.WriteLine($"序列求和结果:{sumResult}");
        
        // IEnumerable<T>.Min(x=>x) 效果,计算整个序列当中的最小元素。
        var minResult = integers.Aggregate((min, next) => min > next ? next : min);
        Console.WriteLine($"序列当中的最小值:{minResult}");

        // IEnumerable<T>.Max(x=>x) 效果,计算整个序列当中最大的元素。
        var maxResult = integers.Aggregate((max, next) => max < next ? next : max);
        Console.WriteLine($"序列当中的最大值:{maxResult}");
        
        // IEnumerable<T>.Count() 效果,计算整个序列当中的元素数量。
        var countResult = integers.Aggregate(0,(count, next) => ++count);
        Console.WriteLine($"序列当中的元素数量:{countResult}");
        
        // IEnumerable<T>.Average(x=>) 效果,计算整个序列当中的平均数。
        int enumerableCount = 0;
        var averageResult = integers.Aggregate(0, (total, next) =>
        {
            total += next;
            enumerableCount++;
            return total;
        },total=>new
        {
            averageValue = total / enumerableCount
        });
        
        Console.WriteLine($"序列的平均值:{averageResult.averageValue}");
    }
}

输出结果:

序列求和结果:55
序列当中的最小值:1
序列当中的最大值:10
序列当中的元素数量:10
序列的平均值:5