- LINQ中提供了很多集合的扩展方法,配合lambda表达式能够简化数据处理
- 不能直接使用,需要引入命名空间。using System.Linq(非泛型)、或using System.Collections.Generic(泛型)
有关类
Enumerable静态类
namespace System.Linq
静态方法
| 方法名 | 说明 |
|---|---|
| Repeat | |
| Range | |
Repeat("abc", 3)//得到一个字符串序列
Range(1, 3) //生成1 2 3序列
IEnumerable
官方声明
我们先去看看公开的.Net4.0的源程序中IEnumerable、IEnumerable、IEnumerator和IEnumerator这四个接口是如何声明的
public interface IEnumerable<out T> : IEnumerable {
new IEnumerator<T> GetEnumerator();
}
public interface IEnumerator<out T> : IDisposable, IEnumerator {
new T Current {
get;
}
}
public interface IEnumerable {
IEnumerator GetEnumerator();
}
public interface IEnumerator {
bool MoveNext();
Object Current {
get;
}
void Reset();
}
接口IEnumerable实现
1、建一个学生数据结构和一个学生集合类:
//student数据结构
class Student
{
public int id;
public string name;
}
//student 集合
class StudentCollection
{
public List<Student> students = new List<Student>();
//公开一个Add()方法以添加数据
public void Add(Student student)
{
students.Add(student);
}
}
接下来添加数据
static void Main(string[] args)
{
StudentCollection sc = new StudentCollection();
sc.Add(new Student { id=0,name="Tony"});
sc.Add(new Student { id=1,name="Micheal"});
sc.Add(new Student { id =2, name = "Amy" });
foreach(var s in sc) {...}
}
当我们想用foreach()遍历的时候,编译器会告诉我们StudentCollection不包含GetEnumerator,不能用foreach遍历。虽然StudentCollection里面有能用遍历的List,但我们不想在属性上迭代,我们想在类上迭代,不能 foreach(var s in sc.students){...}
现在只有把我们的StudentCollection类改造成能foreach的
2、继承接口IEnumerable
构造原生GetEnumrator方法
这种是利用枚举器的工作原理来写的
当我们在类后面加上:IEnumerable后,会加上个返回迭代器的方法GetEnumrator
class StudentCollection:IEnumerable
{
public List<Student> students = new List<Student>();
public void Add(Student student)
{
students.Add(student);
}
public IEnumerator GetEnumerator()
{
throw new NotImplementedException();
}
}
下面按照IEnumetator接口的约定来实现我们的迭代器
迭代器工作的原理是:先调用MoveNext()方法,然后读取Current得到元素,直到MoveNext返回false
Reset应该是回到第一个元素。但根据MSDN上面的说法,Reset 方法提供的 COM 互操作性。它不一定需要实现;相反,实施者只需抛出NotSupportedException。但是,如果您选择执行此操作,则应确保没有调用方依赖于Reset功能
//迭代器
class StudentCollectionEnumerator : IEnumerator
{
//Current返回当前元素
public object Current
{
get
{
throw new NotImplementedException();
}
}
//MoveNext移动到下一个
public bool MoveNext()
{
throw new NotImplementedException();
}
public void Reset()
{
throw new NotImplementedException();
}
}
我们需要3个字段分别放置 元素的位置、元素、元素集。改变后的程序如下
//迭代器
class StudentCollectionEnumerator : IEnumerator
{
private int _index;
private List<Student> _collection;
private Student value;
public StudentCollectionEnumerator(List<Student> colletion)
{
//首先,迭代器初始化,引入元素集 _collection
_collection = colletion;
//把索引 _index设置成-1。
//设置成-1而不是0是因为迭代器首先调用MoveNext,在MoveNext里面我们先把索引+1指向下一个元素,如果索引_index的值初始为0,则第一个元素是元素集[1],第二个元素了
_index = -1;
}
//第二,我们要把object Current改成 IEnumerator.Current,这个是实现迭代器的关键。返回元素
object IEnumerator.Current
{
get { return value; }
}
public bool MoveNext()
{
//第三,在MoveNext方法内累加索引,并从元素集中读取元素。
_index++;
//让索引值超出元素集返回个false值
if (_index >= _collection.Count) { return false; }
else { value = _collection[_index]; }
return true;
}
public void Reset()
{
//第四,在Reset方法内让索引值为-1,不过好像直接抛出错误也成
_index = -1;
}
}
迭代器写好了,我们在StudentColletion类里面调用
class StudentCollection : IEnumerable
{
public List students;
public StudentCollection()
{
students = new List();
}
public void Add(Student student)
{
students.Add(student);
}
public IEnumerator GetEnumerator()
{
return new StudentCollectionEnumerator(students);
}
}
测试运行一下,大功告成!我们实现了可枚举的自己的类
利用List构造GetEnumerator
通过观察,发现迭代器主要就是返回一个元素对象,而StudentColletion里面的students元素集是List的,本身就能枚举,我们可以利用这个不用专门写迭代器来实现枚举
class StudentCollection:IEnumerable
{
public List<Student> students = new List<Student>();
public void Add(Student student)
{
students.Add(student);
}
public IEnumerator GetEnumerator()
{
foreach(var s in students)
{
yield return s;
}
}
}
这样就能实现枚举了,真简单,充分利用了.Net给出的各种可枚举集合,不用再去写GetEnumerator这种累活了
实现一个通用可枚举的类
class MyCollection:IEnumerable
{
public List mycollection = new List();
public void Add(T value)
{
mycollection.Add(value);
}
public IEnumerator GetEnumerator()
{
foreach(var s in mycollection)
{
yield return s;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
foreach (var s in mycollection)
{
yield return s;
}
}
}
测试运行
static void Main(string[] args)
{
MyCollection mc = new MyCollection();
mc.Add(0);
mc.Add(1);
mc.Add(2);
foreach(var s in mc) { Console.WriteLine(s); }
Console.ReadKey();
}
如果想用泛型,通用可以仿写
class MyCollection<T>
{
public List<T> mycollection = new List<T>();
public void Add(T value)
{
mycollection.Add(value);
}
}
查询语法和方法语法
查询语法更像SQL语句,两种写法编译器的编译结果一样。方法语法更实用
方法语法
var items = list.Where(e => e.Salary > 3000).OrderBy(e => e.Age).Select(e => new
{
e.Age,e.Name,XB=e.Gender?"男":"女"
});
查询语法
var items2 = from e in list
where e.Salary > 3000
orderby e.Age
select new { e.Age, e.Name, XB = e.Gender ? "男" : "女" };
详解
取元素子序列
Skip、SkipWhile
Skip:限制结果集。跳过从头开始的指定数量的元素,返回剩余元素的IEnumerable序列
Skip(3) //跳过前三个,取剩余序列
SkipWhile:从头开始跳过序列中满足指定条件的元素,遇到不满足条件的停止执行,返回剩余元素的序列。(故不会取得所有满足条件的序列)
SkipWhile(i => i == 4)
Take、TakeWhile
Take:限制结果集,返回由序列开头数指定数量的元素的序列
list.Skip(3).Take(2) //跳过前三条,只取两条
Take(20) //只取前20条,不够20条则有多少取多少
TakeWhile:从头开始取返回满足条件的元素的序列,遇到不满足条件的停止执行。(故不会取得所有满足条件的序列)
TakeWhile(i => i == 4)
Where
Where(i=>i<7) //
Where((i, k) => i % 2==0) //选择值为偶数的元素
Where((i, k) => k% 2==0) //选择为偶数位置的元素
Where((m,k)=>(k>=i&&k<=j)) //取指定区间 [i,j] 的子序列
模仿Where原理
static IEnumerable<int> MyWhere1(IEnumerable<int> items,Func<int,bool> f)
{
List<int> result = new List<int>();
foreach(int item in items)
{
if(f(item)) result.Add(item);
}
return result;
}
优化:使用yield return
static IEnumerable<int> MyWhere1(IEnumerable<int> items,Func<int,bool> f)
{
foreach(int item in items)
{
if(f(item)) yield return item;
}
}
Distinct
去重
OfType
OfType<int>() //筛选元素类型是int的序列
投影操作
Select
方法介绍
//参数1是数据项,参数2是下标
Select((i,k) => L(nums, k))
类型转换
Select(i=>Int32.Parse(i.ToString())) //将序列中的数字字符串转换为数值
Select(i => (double)i)
Select(i => i .ToString())
和匿名类型、var配合使用
var items= list.Select(e => new {name=e.Name,age=e.Age,gender=e.Gender?"男":"女"})
示例
Select(p=>p.name) //只取每个对象的name属性
Select(i => i ==2) //判断序列中的每个元素是否满足某条件,返回true false true这样的序列
//按年龄分组,求各组的最大工资、各组人数
var items = list.GroupBy(e => e.Age)
.Select(g => new { age = g.Key, MaxS = g.Max(e => e.Salary),
Numbers = g.Count() });
//替代逆序for循环
foreach(int i in nums.Select((m, k) => k).Reverse())
聚合函数
Sum
Max、Min
也可以做字符串比较
Average
返回值double
Count
元素个数或满足条件的元素个数
items.Count(i=>i>1)
items.Count()
Aggregate
可以设置迭代对象的初始值。若不设置,则默认第一个数据为迭代对象初值,从第二个数据开始迭代。
序列中的元素求和、拼接字符串
Aggregate((i, j) => i + j) //int求和、string拼接字符串。参数1为迭代的对象,参数2为下一个对象
拼接序列中的元素
//拼接数组中的字符串元素,并指定分隔符(这里是空格)
Aggregate((i,j)=>i+" "+j)
//反转方式拼接字符串
Aggregate((i,j)=>j+" "+i)
最大值、最小值
//最大值
Aggregate((i,k)=>i>k?i:k)
//最小值
Aggregate((i,k)=>i<k?i:k)
删除或替换字符串中的指定字符
//删除str字符串中的指定字符(在InvalidChar中)
InvalidChar.Aggregate(str, (m, k) => m.Replace(k+"",""))
序列的运算
Union
两个序列的并集(去重后),返回新的序列
Intersect
参数为另一个序列,返回交集序列
Except
参数为序列2,返回序列1中去除序列2后剩余的序列
zip
将任意函数应用于两个序列的对应元素
Zip(b, (i, j) => i * j)
SequenceEqual
判断序列对应位置元素是否相等
Concat
连接两个序列
取单个元素
First、FitstOrDefault
- First:至少有一条,返回第一条。没有则报错
- FitstOrDefault:取第一条,一条都没有则取默认值
示例
list.First()
list.First(i=>i>3)
Last、LastOrDefault
同
ElementAt、ElementAtOrDefault
参数为下标,得到指定下标的元素
ElementAtOrDefault:索引超出范围返回默认值(指元素未赋值的缺失值)
Single、SingleOrDefault
- Single:集合中有且只有一条满足条件的数据,没有则引发异常
- SingleOrDefault:集合中最多有一条满足条件的数据,找到则返回,找不到返回默认值,多条则报错
示例
list.Single() //集合中有且只有一条数据,则返回
list.Single(i=>i==5)
添加元素
Prepend、Append
Prepend:向序列开头添加元素,返回新序列
Append:向序列结尾添加元素,返回新序列
排序
OrderBy、OrderByDescending
- OrderBy:正序(升序)排序,排序规则可任意指定
- OrderByDescending:倒序(降序)排序
示例
list.OrderBy(i=>i.age)
list.OrderBy(i=>i)
//随机排序
Random rand = new Random();
list.OrderBy(i=> rand.Next());
ThenBy、ThenByDescending
注意:不要写两个OrderBy,否则只会根据最后一个OrderBy排序
IOrderedEnumerable对象才可使用,常跟在OrderBy后面,用来进行多级排序。
示例
list.OrderBy(i=> i.age).ThenBy(i=>i.salary)
序列类型转换
ToArray、ToList
查找元素
Contains
Any
- 无参时:判断是否为空(容器内是否有元素),为空返回flase,如list。注意不适用于数组,分配空间后即有默认字则为true,除非new int[0]不分配内存空间
- 单参时,判断集合中是否至少有一条满足条件。Any比Count更高效,类似短路运算,Count需要数完再判断。
示例
items.Any() //容器内是否包含元素
items.Any(i=>i>3)
ALL
判断是否所有元素都满足条件
分组
GroupBy
返回值序列中的每一组是一个IGrouping对象,IGrouping继承自IEnumerable
| 参数 | 说明 |
|---|---|
| 参数1 | 按照什么进行分组,同时作为回调函数的第一个参数 |
| 参数2 | 取对象的哪个属性作为元素,同时作为回调函数的第二个参数 |
| 参数3 | 回调函数,同时定义了每个分组可以访问的元素属性 |
示例
//元素是键值对,按照键值对的值分组
dict.GroupBy(i=>i.Value);
//按对象的年龄字段进行分组,返回类型为IEnumerable<IGrouping<int,Empolyee>>,IGrouping对象的key属性是Age,IGrouping的每个元素是真正的对象
list.GroupBy(i=>i.Age)
//每个元素作为一个分组
GroupBy(i=>i)
//按照是否满足条件分成两组
GroupBy(i => i % 2 == 0)
//按照年龄分组后按照年龄排序,并投影为年龄、人数、平均工资
list.GroupBy(e => e.Age).OrderBy(e => e.Key).Select(g => new { NL = g.Key, RS = g.Count(), PJ = g.Average(e => e.Salary) });
示例2
static void MyWhere1(IEnumerable<int> list, Func<int,bool> f)
{
IEnumerable<IGrouping<int, Empolyee>> items = list.GroupBy(e => e.Age);
foreach(IGrouping<int,Emoloyee> g in items)
{
Console.WriteLine(g.Key);
foreach(Employee e in g)
{
Console.WriteLine(e);
}
}
}
反转序列
Reverse
LINQ题目
统计一个字符串中每个字母出现的频率(忽略大小写),然后按照从高到低的顺序输出出现频率高于2次的字母和其出现的频率
//先拿到字符串中的所有字母,并转换为小写
//按照每个字母分组
//每个分组取统计项,字母名、数量
//按照数量从高到低排序
//筛选数量大于2的数据
str.Where(c => char.IsLetter(c)).Select(c => char.ToLower(c)).GroupBy(c => c)
.Select(g => new { g.Key, Count = g.Count() })
.OrderByDescending(g => g.Count).Where(g => g.Count > 2);