十四、支持标准查询操作符的集合接口
14.1 匿名类型与隐式变量
- 隐式局部变量 var
- 只能用于局部变量
- 不可以作为方法的参数类型
- 匿名类型
- 由编译器声明的类型
- new { key1=value1, key2=value2 }
- 不是没有类型,而是编译器根据具体的值自动生成类型class
- 不可变性
- 一旦实例化,属性或字段都不可变
- 类型兼容
- 属性名称、数据类型和属性顺序都必须完全匹配
- 相同属性,属性顺序不同也会生成不同的匿名类
- 类型兼容的,使用相同的类进行实例化
- ToString()等方法被重写
- 按{ key1=value1, key2=value2 }的形式输出
- 由编译器声明的类型
14.2 集合初始化器
14.2.1 集合初始化器
- 类似数组声明,在构造函数调用的后面添加大括号
- 由编译器调用集合的Add()方法
- 条件
- 只要集合中定义了Add()方法
- 例如,实现ICollection<T>接口
- 或者在实现IEnumerable<T>接口的类型的基础上有Add()方法
- 只要集合中定义了Add()方法
14.2.2 字典类型的初始化
- C#6.0
- new Directory<key,value> { [key1]=value1, [key2]=value2 }
- C#6.0之前
- new Directory<key,value> { {key1,value1}, {key2,value2} }
14.2.3 初始化匿名类型数组
var A = new[]
{
new
{
key1=value1,
key2=value2
},
new
{
key1=value1,
key2=value2
}
}
14.3 集合接口IEnumerable<T>
14.3.1 数组的foreach
- 在CIL中也是通过for实现的
- 需要知道数组的长度以及索引操作符的支持,即length-index模式
14.3.2 集合遍历
- 迭代器模式
- 确定第一个元素、下一个元素和最后一个元素
- 不需要事先知道元素总数,也不需要按照索引获取元素
- IEnumerator、IEnumerator<T>
- MoveNext()
- Current:只读属性
- 问题
- 交错:foreach嵌套和多线程循环
- 错误处理:循环中出现异常
- IEnumerable、IEnumerable<T>
- GetEnumerator()
- 返回一个实现了IEnumerator接口的类实例,一般是集合的内部嵌套类
- 每个实例维护循环遍历的状态
- IEnumerator<T>派生于IDisposable,需要实现Dispose()方法释放保存的状态
- GetEnumerator()
- Duck Typing
- If it walks like a duck and quacks like a duck, it must be a duck
- 集合不一定要实现IEnumerable接口,只需该集合具备GetEnumerator()方法,并返回带有MoveNext()和Current类型的对象就行
- 集合遍历是不能对集合进行增、删、改操作
14.4 标准查询操作符
- Linq
- Language Integrated Query
14.4.1 System.Linq.Enumerable类
- 为IEnumerable<T> 接口拓展了很多方法
- 每个方法都是标准查询操作符,用于从集合中筛选数据
14.4.2 AsParallel()
- System.Linq.ParallelEnumerable拓展方法
- 集合遍历与执行表达式操作并行运行
14.4.3 Count()
- Count()
- 可以接收Func<T, bool>作为参数,对满足条件的元素计数
- 会遍历集合,尽量避免
- 判断集合是否有元素应使用Any()
- 集合的Count属性
- 如果集合实现了ICollection<T>接口,优先使用Count属性,会调用内建的Count机制
14.4.4 推迟执行
- 将Lambda表达式赋值时,Lambda表达式不会立即执行
- 只有在遍历集合时Lambda表达式才被执行
14.4.5 ToXXX()
- 会遍历集合,并会返回一个新的结果缓存在内存中
- 利用缓存对象进行操作可以不涉及查询表达式
14.4.6 OrderBy()、ThenBy()
- OrderBy()
- 返回IOrderedEnumerable<T>接口,该接口派生于IEnumerable<T>
- ThenBy()
- 额外排序条件
- System.Linq.Extensions.Enumerable中拓展IOrderedEnumerable<T>接口的
- 执行后续的排序操作时会重复调用之前的keySelector Lambda表达式
- OrderByDescending()、ThenByDescending()
14.4.7 Join()
- 将两个集合进行内联,例如,A.Join( B, keyA, keyB, ( A, B ) => new { result } )
- 匿名类型用于生成返回结果
14.4.8 GroupBy()
- 对一个集合进行分组,结果数等于组数,例如,A.GroupBy( keySelector )
- 返回 IGrouping<TKey,TElement> 类型的集合
- 每个 IGrouping<TKey,TElement> 是根据keySelector得到的一个分组集合
- IGrouping<TKey,TElement> 派生于IEnumerable<T>
- 该接口有一个属性指定了分组依据的键
- 可以通过 foreach ( TElement in IGrouping) 遍历element
- 一个IGrouping是一个组,该组有一个键,多个element
14.4.9 GroupJoin()
- 分组联接
- 先分组,再根据组的键值进行联接
- 一对多
- Lambda表达式对应的委托类型是Func<T, IEnumerable<T>, TResult>
- 联接不匹配时返回空集合,相当于左外联接
var items = departments.GroupJoin(
employees,
department=>department.Id,
employee=>employee.DepartmentId,
(department, departmentEmployees)=>new
{
department.Id,
department.Name,
Employees=departmentEmployees
});
foreach(var item in items)
{
Console.WriteLine(item.Name);
foreach(Emplyee employee in item.Employees)
{
Console.WriteLine(employee);
}
}
14.4.10 SelectMany()
- 接收参数
- Func<TSource, IEnumerable<TResult>>
- 用于将集合中的集合汇总到一个集合
- 例如,teams数组中,每一个team都有一个包含球员的数组,SelectMany()返回所有球队的球员集合
- Func<TSource, IEnumerable<TResult>>
14.4.11 DefaultIfEmpty()
- 集合查询结果若为空,则返回该类型的默认值
- 括号中也可指定其默认值
14.4.12 其他API方法
14.4.13 IQuerable<T>
- 派生自IEnumerable<T>接口,但不继承其拓展方法
- System.Linq.Queable类为IQuerable<T>接口拓展了类似方法
- 通过IQuerable<T>接口可以实现自定义的LINQ Provider,为将Lambda表达式转换为其他语言如SQL提供一种解释机制