在C#2.0的时候出现了匿名方法,在3.0的时候出现了Linq,

这里讲述的linq就是Linq to object的实现,与to sql的底层是由差别的,但是语言层面的用法是差不多的。

核心记住:序列与集合不同,序列有延迟计算的性质,集合虽然有序列的性质,但是集合的内部元素往往都是确定的。实现Linq必会实现装箱与拆箱,注意消耗。

一、实现foreach的核心->IEnumerable与IEnumerator

这是两个接口,我们称之为序列,或者是迭代器。这是实现Linq的核心,也是实现foreach的核心功能的主要接口。

那么现在来看看foreach是如何实现的:

  1. //这是一种仿照foreach的遍历集合的方法
  2. List<int> s = new List<int>{1,5,3,7,23};
  3. var enumerator = s.GetEnumerator();
  4. while (enumerator.MoveNext())
  5. {
  6. Console.WriteLine(enumerator.Current);
  7. }

上面这种方式实现了核心的foreach方法,当然实际的方法会比这种更加细节,如判断异常等功能。

之后就是要了解迭代器IEnumerable,IEnumerator的两个接口,这两个一个是公开枚举数,一个是枚举器,支持简单迭代。理解:一个集合实现IEnumerable接口,就可以用IEnumerator进行简单迭代。这也就理解了list,hashset为什么可以用foreach进行迭代的原理。

这里我们利用yield return来返回一个公开枚举数:

  1. //这段代码很好理解:声明一个变量,返回一个公开枚举数,序列里包含着是1~100的字符串序列。这里的yield return与IEnumerable搭配使用,返回一个公开枚举数。我们称为序列。
  2. public static IEnumerable<string> GenerateSequence()
  3. {
  4. var i = 0;
  5. while (i++<100)
  6. {
  7. yield return i.ToString();
  8. }
  9. }
  1. public static IEnumerable<string> SequenceFormConsole()
  2. {
  3. string text = Console.ReadLine();
  4. while (text != "done")
  5. {
  6. yield return text;
  7. text = Console.ReadLine();
  8. }
  9. }

序列的短路性质且具有延迟计算的性质,下面的例子有很好的解释性:

  1. var input = SequenceFormConsole().Select(x => int.Parse(x));
  2. foreach (var item in input)
  3. {
  4. Console.WriteLine($"\t{item}");
  5. }

二、Select的实现用法与内涵

调用Select是为了生成一个新的序列(一个满足我们需求的序列),在官方API中的解释是:通过合并元素的索引投影到一个新窗体的序列的每一个元素。

  1. //这是一个简单得利用Linq的Select实现索引的效果,GenerateSequence()这个方法就是上面的例子方法。
  2. var sequence = GenerateSequence().Select((a,index) =>
  3. new
  4. {
  5. index,
  6. a
  7. });
  8. foreach (var item in sequence)
  9. {
  10. Console.WriteLine(item);
  11. }

Select原理核心实现:

  1. //这是一种简单的原理实现,但是缺点也很明显,泛型类型确定,实现特定,扩展性差。
  2. public static IEnumerable<string> Select(this IEnumerable<int> source,Func<int,string> selector)
  3. {
  4. foreach (var item in source)
  5. {
  6. yield return selector(item);
  7. }
  8. }
  9. //这种是最接近底层的一种实现,扩展性良好。
  10. public static IEnumerable<TResult> Select<TSource,TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector)
  11. {
  12. foreach (TSource item in source)
  13. {
  14. yield return selector(item);
  15. }
  16. }

 三、where的实现用法与内涵

  1. //where是一个静态方法,在LinQ的语法中,他可以当做一个筛选器。
  2. var sequence = GenerateSequence2().Where(x => x%5 == 0).Select((x,index)=>
  3. new
  4. {
  5. index,
  6. formatted = x.ToString()
  7. });
  8. foreach (var item in sequence)
  9. {
  10. Console.WriteLine(item);
  11. }

Where的核心原理实现:

  1. /// <summary>
  2. /// 这是一种无参数的序列判断,当然这正方式并不好,我们要自己的定义判断函数,这就需要一种方法,也就是委托的实现,这种方式能极大的满足lambda函数的可重用性质
  3. /// </summary>
  4. /// <param name="source"></param>
  5. /// <returns></returns>
  6. public static IEnumerable<string> Where(this IEnumerable<string> source)
  7. {
  8. foreach (string item in source)
  9. {
  10. if (item.Length<2)
  11. {
  12. yield return item;
  13. }
  14. }
  15. }
  16. /// <summary>
  17. /// 这是一个Where的实现,也是一个Where的重载方法,是上面的重用扩展,这种方法的扩展性会更好,但还不是最好
  18. /// 由这个方法可知,这种只能传递一个String的参数,所以扩展性不能说最好,为了满足可重用性的原则,我们需要
  19. /// 将这个方法的泛型改为通用泛型T
  20. /// </summary>
  21. /// <param name="source"></param>
  22. /// <param name="predicate"></param>
  23. /// <returns></returns>
  24. public static IEnumerable<string> Where(this IEnumerable<string> source,Func<string,bool> predicate)
  25. {
  26. foreach (string item in source)
  27. {
  28. //这里实现了委托的传递参数,函数为:x=>x.Length<2
  29. if (predicate(item))
  30. {
  31. yield return item;
  32. }
  33. }
  34. }
  35. /// <summary>
  36. /// 这种方式第三种形式,泛型的重用,实现了自定义泛型
  37. /// </summary>
  38. /// <param name="source"></param>
  39. /// <param name="predicate"></param>
  40. /// <returns></returns>
  41. public static IEnumerable<T> Where<T>(this IEnumerable<T> source, Func<T, bool> predicate)
  42. {
  43. foreach (T item in source)
  44. {
  45. //这里实现了委托的传递参数,函数为:x=>x.Length<2
  46. if (predicate(item))
  47. {
  48. yield return item;
  49. }
  50. }
  51. }

四、Any的实现用法与内涵

作用:确定序列是否包含任何元素,可以传递Lambda函数来判断。

  1. //any方法
  2. Console.WriteLine(SequenceFormConsole().Any(x=>x=="hello"));

Any的核心实现

  1. /// <summary>
  2. /// 实现了Any的重载,确定一个序列是否包含一个值,返回bool类型。
  3. /// </summary>
  4. /// <typeparam name="T"></typeparam>
  5. /// <param name="source"></param>
  6. /// <param name="predicate"></param>
  7. /// <returns></returns>
  8. public static bool Any<T>(this IEnumerable<T> source, Func<T, bool> predicate)
  9. {
  10. //调用where的方法判断序列是否有值并返回此值,这样调用他的子类就可以确定次序列是否有此项。
  11. return source.Where(predicate).GetEnumerator().MoveNext();
  12. }

五、Count的实现用法与内涵

这个方法用于实现计数

  1. //count方法
  2. Console.WriteLine(SequenceFormConsole().Count(x=>x.Contains("hello")));

Count的核心实现

  1. /// <summary>
  2. /// 实现count的核心
  3. /// </summary>
  4. /// <typeparam name="T"></typeparam>
  5. /// <param name="source"></param>
  6. /// <returns></returns>
  7. public static int Count<T>(this IEnumerable<T> source)
  8. {
  9. var count = 0;
  10. while (source.GetEnumerator().MoveNext()!=false)
  11. {
  12. count++;
  13. }
  14. return count;
  15. }
  16. /// <summary>
  17. /// 实现count的函数重载
  18. /// </summary>
  19. /// <typeparam name="T"></typeparam>
  20. /// <param name="source"></param>
  21. /// <param name="predicate"></param>
  22. /// <returns></returns>
  23. public static int Count<T>(this IEnumerable<T> source, Func<T, bool> predicate)
  24. {
  25. return source.Where(predicate).Count();
  26. }

这里的Count:元素总量 sum:元素总和 max:元素最大值 min:元素最小值 与后面的Aggregate都是-聚合函数,用法都差不多,但是性能差别却很大,用的时候小心且要注意。

六、Aggregate的用法与内涵

这是一个累加器,与Count类似,但是也有很大区别,他有更广泛的用法,不仅仅是实现计数,还可以实现如字符串的累加(拼接)等等。下面就是实现一个拼接的例子。

Console.WriteLine(SequenceFormConsole().Aggregate((x, y) => x + "," + y));

核心实现:

/// <summary>
/// 实现一个累加器的方法,这种方式更加的通用,对于任意类型都可以实现特的函数方式的累加。
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source">方法调用者</param>
/// <param name="func">一个方法函数</param>
/// <returns></returns>
public static T Aggregate<T>(this IEnumerable<T> source, Func<T, T, T> func)
{
    var enumerator = source.GetEnumerator();
    enumerator.MoveNext();
    var sum = enumerator.Current;
    while (enumerator.MoveNext())
    {
        sum = func(sum, enumerator.Current);
    }
    return sum;
}

七、OrderBy与ThenBy的使用

这两个方法为排序方法,这里不做实现,实现很复杂,只要求熟练掌握并会使用就行。具体看示例:

这里有一个排序语法:descending 降序排列 。 默认升序ascending。

//OrderBy(排序)方法:这里的orderby与thenby方法并不会具体实现,这种排序方法实际上非常复杂,你可以尝试去阅读一些源码,这里索
//要知道的只有 1、排序方法非常消耗计算机资源,不只是C#,在java,object-c等等语言上都是这样,但是这种消耗的资源我们会尽量的
//去少消耗 2、了解一些排序的原理,这里的排序不会去根据不同的lambda函数一次一次的遍历序列,直至返回排序的序列,而是等待着,根
//根据所有的lambda函数,去中和出一个满足所有lambda函数的排序规则,一次性的遍历出满足所有的lamabd函数的序列并返回,这种方式
//无疑是效率很高的一种方法,不必去多次排序序列。3、合理使用OrderBy与ThenBy。
var sequences = SequenceFormConsole().OrderBy(x => x.Length).ThenBy(x => x);
foreach (var item in sequences)
{
    Console.WriteLine(item);
}

八、总结与一些性能的优化与选择

性能是难以衡量的,在面对不同的工作环境时,一个算法可能并不适用所有的工作环境,取决于任务量,复杂度等等因素,Linq也是这样的一个库,在一些大多数工作环境中他往往可以胜任,但是有一些工作比如要追求极致的性能提升,可能Linq比不适用,也可能一个任务可能用Linq的一个方法比用另一个方法更快也更简洁,这就需要开发者去探索,去开发。这也正是编程的乐趣所在。下面我们就通过一些例子来分析一下那些在Linq中的有趣的方法案例。

1、有时候Any比Count更好

//在查询一个集合是否为空的时候,可以选择Count来实现计数比较。但是在集合达到百万级别(不同电脑的性能)的时候,这时候Count可能就不适合了,这个统计会统计全局的参数,而Any则具有短路的性质,这样他可以更快地完成任务。
var sequence = Enumerable.Range(0,30000000);
//你可以使用Count这样写:
sequence.Count() != null;
//你也可以用Any这样写:
sequence.Any();
//当然,这两种写法的执行效率是不同的。在数据量少时可能没区别,但是数据量过大时,就是指数级的性能提升。

但是有时往往会有偏差,比如我想统计一个餐馆来过三次以上的客人:这是后的Any就不适用,因为谈返回的结果可能不是我想要的,但是借助其他的方法,可能就是实现性能上的提升。

//介绍一些Take()与Skip();
//两个方法很好理解:Take():拿到指定数目的序列并返回。
//                 Skip():跳过指定数目的序列,并返回剩余的序列。

//TakeWhile()与SkipWhile();
//这两个方法都具有短路性质,他们是从序列的头开始判断,只要不满足表达式,就执行方法,且短路执行。
//TakeWhile():返回满足表示的的序列元素。但只要第一项不满足,那就之后短路,返回空序列。
//SkipWhile():跳过满足表示的的序列元素,返回其余元素。但只要第一项不满足,那就之后短路,返回整个原序列。
var sequence = Enumerable.Range(0,30000000);
//你可以使用Count这样写:
sequence.Count() > 3;
//你也可以用Any这样写:
sequence.Skip(3).Any();
//这种方式在一定数量级上会实现性能上的提升,但是假如数量是小于10的,那可能Count会更块一些,因为使用下面的方法回创建两个枚举器,这个代价是很大的,在一定程度上的选择可能会实现最佳,但是往往没有绝对的。

之后的我不想多做介绍了,不同的工作有不同的工作方式,剩下的还需要在工作中慢慢体会,理解。

九、Linq To Object的简单原理实现与总结

总之,Linq To Object就是用迭代器+泛型+委托+扩展方法 形成的这么一个方法,他可以去对实现了IEnumerable接口的集合进行一些操作,本质上是一些操作集合的API。

下面是一个对linq to object 的where方法做简单的实现,可见与普通方法的区别,其中重要的就是:

扩展方法:实现了一种功能上的调用,与实例方法与静态方法的调用不同,特可以直接由方法的第一个参数类型 以 点(.) 的方法出现。作用就是快捷,方便,便于查阅。

泛型:免于拆箱与装箱,为性能提供了保障。提高了代码的重用性。

委托:提升了代码的重用性,逻辑更好的保证了。

迭代器:可以用延迟加载的思想。

/// <summary>
/// 扩展方法:要求类必须开放且静态,且其中的方法也要开放且静态、方法的第一个参数用this关键字修饰。
/// </summary>
public static class LinqExtend
{
    // 普通的扩展泛型集合,这种是用固定的集合做为返回值,局限较大,也没有IEnumerable的延迟查询的这种特性。
    public static IList<T> WhereExtend<T>(this IList<T> ts, Func<T, bool> func)
    {
        IList<T> ts1 = new List<T>();
        foreach (var item in ts)
        {
            if (func(item))
            {
                ts1.Add(item);
            }
        }
        return ts1;
    }
    // 这是一个用IEnumerable作为返回值的,他可以实现延迟加载的功能特性。
    public static IEnumerable<T> degelateWhere<T>(this IEnumerable<T> ls, Func<T, bool> func)
    {
        foreach (var item in ls)
        {
            if (func(item))
            {
                // 这里用状态机来实现延迟的思想。
                yield return item;
            }
        }
    }
}