一、接口实现Where扩展
上一章我们最终得到的where扩展方法实现如图,但是还是有缺点:只支持List调用改扩展方法,那么能不能让它更加灵活呢?
当然可以:可以通过IEnumerable接口来扩展,这样做的好处:
- 通过接口来扩展,只要实现了这个接口的,都可以使用这个扩展(如List,HashSet等)
- 扩展性更好,更加通用
/// <summary>
///
/// 将原来的List<T>改为IEnumerable<T>
/// 优点是什么?
///
/// 1、通过接口来扩展,只要实现了这个接口的,都可以使用这个扩展
/// 2、扩展性更好,更加通用
///
/// </summary>
/// <param name="resource"></param>
/// <param name="func"></param>
/// <returns></returns>
public static IEnumerable<T> WhereEnumerable<T>(this IEnumerable<T> resource, Func<T, bool> func) where T : class
{
var resultList = new List<T>();
//可以使用for循环遍历,将满足条件的随想添加到resultList集合中
foreach (var item in resource)
{
if (func.Invoke(item))
{
resultList.Add(item);
}
}
return resultList;
}
思考:还能优化吗?
二、yield 迭代器关键字
- yield关键字必须与IEnumerable配合使用,二者密不可分
- 按需取用:找到一个,取出并用一个,然后再找下一个
/// <summary>
///
/// yield关键字必须与IEnumerable<T>配合使用,二者密不可分
///
/// 优点是什么?
///
/// 1、按需取用:找到一个,取出并用一个,然后再找下一个
///
/// </summary>
/// <param name="resource"></param>
/// <param name="func"></param>
/// <returns></returns>
public static IEnumerable<T> WhereIterator<T>(this IEnumerable<T> resource, Func<T, bool> func) where T : class
{
foreach (var item in resource)
{
Console.WriteLine("***************开始yield*****************");
if (func.Invoke(item))
{
yield return item;
}
}
}
对比
我们再循环里都打印一句话,如图:
调用
观察结果:
- WhereEnumerable方法是循环完成之后将结果返回
- WhereIterator方法是找到一个就直接返回并使用,然后再进入循环查找下一个
二者的代码运行顺序也是不同的:
(1)WhereEnumerable方法:
可以看到,点击F10之后,断点立刻进入WhereEnumerable方法中,步骤如下:
- 完成所有循环,返回结果
- 然后在调用方使用返回结果。
(2)WhereIterator方法:
可以看到,点击F10之后,断点不是立刻进入WhereIterator方法中,具体步骤如下:
- 而是先到返回结果使用的地方
- 然后再进入WhereIterator方法中,循环循环,一旦找到第一个匹配结果,立马返回到返回结果使用的地方使用该匹配结果
- 然后再次进入WhereIterator寻找匹配的结果(此时直接进入循环中匹配并返回结果,并不会再循行foreach这行代码)
本质
yield 关键字的本质是一个迭代器状态机,加了yield关键字的方法通过反编译工具反编译之后可以看到:
- 自动生成了一个泛型类副本
- 自动在方法上添加了IteratorStateMachine特性,参数是生成的泛型类
核心
迭代器状态机的核心要义:只要找到一个符合当前条件的数据,就直接返回。
通过反编译,我们可以在生成的泛型类中找到MoveNext方法,如下
上图可以看到,核心就是通过递归找到符合条件的数据,然后直接返回并使用,而后将状态恢复到原始状态,接着再次寻找下一个符合条件的数据并返回。
三、linq中where的本质
把固定不变的逻辑封装起来,把可变的逻辑封装成委托来传递。