• 扩展方法和Enumerable


    LINQ是我最喜欢的功能之一,程序中到处是data.Where(x=x>5).Select(x)等等的代码,她使代码看起来更好,更容易编写,使用起来也超级方便,foreach使循环更加容易,而不用for int..,linq用起来那么爽,那么linq内部是如何实现的?我们如何自定义linq?我们这里说的linq不是from score in scores  where score > 80 select score;而是System.Linq哦。了解Ling之前先要了解扩展方法,因为linq的实质还是扩展方法。

    扩展方法

    扩展方法使你能够向现有类型“添加”方法,而无需创建新的派生类型、重新编译或以其他方式修改原始类型。 扩展方法是一种特殊的静态方法,但可以像扩展类型上的实例方法一样进行调用。 

    例如:

    复制代码
    namespace ExtensionMethods
    {
        public static class MyExtensions
        {
            public static int WordCount(this string str)
            {
                return str.Split(new char[] { ' ', '.', '?' }, 
                                 StringSplitOptions.RemoveEmptyEntries).Length;
            }
        }   
    }
    //添加引用
    using ExtensionMethods;  
    //使用
    string s = "Hello Extension Methods";  
    int i = s.WordCount();  
    复制代码

    微软扩展方法建议

    微软MSDN上的建议:通常,建议只在不得已的情况下才实现扩展方法,并谨慎地实现。只要有可能,都应该通过创建从现有类型派生的新类型来达到这一目的。

    扩展方法建议

    1. 当功能与扩展类型最相关时,可以考虑使用扩展方法。
    2. 当对第三方库进行扩充的时候,可以考虑使用扩展方法。
    3. 当您不希望将某些依赖项与扩展类型混合使用时,可以使用扩展方法来实现关注点分离。
    4. 如果不确定到底使用还是不使用扩展方法,那就不要用。

    扩展方法是C#语言的一个很好的补充,她使我们能够编写更好,更容易读的代码,但是也应该小心使用,不恰当的使用扩展方法可能导致可读性降低,使测试困难,容易出错。

    System.Linq

    System.Linq用起来那么好,她内部是如何实现的,当然是查看源码了。

    Where源码

    复制代码
    public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) {
        if (source == null) throw Error.ArgumentNull("source");
        if (predicate == null) throw Error.ArgumentNull("predicate");
        if (source is Iterator<TSource>) return ((Iterator<TSource>)source).Where(predicate);
        if (source is TSource[]) return new WhereArrayIterator<TSource>((TSource[])source, predicate);
        if (source is List<TSource>) return new WhereListIterator<TSource>((List<TSource>)source, predicate);
        return new WhereEnumerableIterator<TSource>(source, predicate);
    }
    复制代码

    这个方法就是一个扩展方法,对数据进行了处理,具体的处理都是在对象中的MoveNext中

    复制代码
    public override bool MoveNext() {
        if (state == 1) {
            while (index < source.Length) {
                TSource item = source[index];
                index++;
                if (predicate(item)) {
                    current = item;
                    return true;
                }
            }
            Dispose();
        }
        return false;
    }
    复制代码

    可以看出就是一个循环处理,如果你觉得还是不清楚,可以看WhereIterator方法

    复制代码
    static IEnumerable<TSource> WhereIterator<TSource>(IEnumerable<TSource> source, Func<TSource, int, bool> predicate) {
        int index = -1;
        foreach (TSource element in source) {
            checked { index++; }
            if (predicate(element, index)) yield return element;
        }
    }
    复制代码

    这下明白了,linq就是扩展方法,对数据进行处理,返回所需要的数据,知道了原理之后,可以写自己的linq扩展方法了。
    我想写一个带有控制台输出的Where扩展方法

    复制代码
    public static IEnumerable<TSource> WhereWithLog<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
    {
        if (source == null)
        {
            throw new ArgumentNullException("source", "不能为空");
        }
    
        if (predicate == null)
        {
            throw new ArgumentNullException("predicate", "不能为空");
        }
    
    
        int index = 0;
        foreach (var item in source)
        {
            Console.WriteLine($"Where item:{item},结果:{predicate(item)}");
            if (predicate(item))
            {
                yield return item;
            }
            index++;
        }
    }
    复制代码

    实现一个打乱数据的扩展方法,这里的方法用了约束,只能是值类型。

    复制代码
    public static IEnumerable<T> ShuffleForStruct<T>(this IEnumerable<T> source) where T : struct
    {
        if (source == null)
            throw new ArgumentNullException("source", "不能为空");
    
        var data = source.ToList();
        int length = data.Count() - 1;
        for (int i = length; i > 0; i--)
        {
            int j = rd.Next(i + 1);
            var temp = data[j];
            data[j] = data[i];
            data[i] = temp;
        }
    
        return data;
    }
    复制代码

    到此为止是不是觉得Enumerable中的方法也就是那么回事,没有那么难,我也可以实现。

    应评论的需要增加whereif,条件为true执行左边的条件,false执行右边的条件,例如:data.WhereIf(x => x > 5, x => x + 10, x => x - 10)

    复制代码
    public static IEnumerable<TSource> WhereIf<TSource>(this IEnumerable<TSource> source, Func<TSource,bool> predicate, Func<TSource, TSource> truePredicate, Func<TSource, TSource> falsePredicate)
    {
        if (source == null)
        {
            throw new ArgumentNullException("source", "不能为空");
        }
    
        if (predicate == null)
        {
            throw new ArgumentNullException("predicate", "不能为空");
        }
    
        if (truePredicate == null)
        {
            throw new ArgumentNullException("truePredicate", "不能为空");
        }
    
        if (falsePredicate == null)
        {
            throw new ArgumentNullException("falsePredicate", "不能为空");
        }
    
        foreach (var item in source)
        {
            if (predicate(item))
            {
                yield return truePredicate(item);
            }
            else {
                yield return falsePredicate(item);
            }
        }
    }
    复制代码

    或者简单的whereif,true执行条件,false不执行,例如:data.WhereIf(x => x > 5,true)

    public static IEnumerable<TSource> WhereIf<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate, bool condition)
    {
        return condition ? source.Where(predicate) : source;
    }

     其他的linq文章

    1. Linq的执行效率及优化

    2. C#中linq

  • 相关阅读:
    UVA10870—Recurrences(简单矩阵快速幂)
    ZOJ3690—Choosing number
    poj3735—Training little cats(特殊操作转化为矩阵操作)
    确定opencv矩阵元素类型
    Ubuntu常用操作
    编译ffmpeg + x264 + cuda + opencv
    搭建Nginx+rtmp直播服务器
    树莓派LCD显示器安装步骤
    使用 nginx 和 rtmp 插件搭建视频直播和点播服务器
    Arduino-UNO MPU9250/6500
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/11105145.html
Copyright © 2020-2023  润新知