Linq使用心得——伪造一个SelectMany

        上篇《Linq使用心得——SelectMany替代二重foreach循环》中我们学习了SelectMany的一些用法。不小心给韦恩卑鄙这个家伙看到了,他就唆使我写如何伪造一个SelectMany方法。这真是赶鸭子上架啊,所以今天我们就来试试看吧。其实也没啥好说的,直接上代码吧。

    static class FakeLinq
    {
        public static IEnumerable<TResult> FakeSelectMany<TSource, TResult>(
            this IEnumerable<TSource> source,Func<TSource, IEnumerable<TResult>> selector)
        {
            foreach (var s in source)
            {
                foreach (var r in selector(s))
                {
                    yield return r;
                }
            }
        }

        public static IEnumerable<TResult> FakeSelectMany<TSource, TResult>(
            this IEnumerable<TSource> source,Func<TSource, int, IEnumerable<TResult>> selector)
        {
            int index = 0;
            foreach (var s in source)
            {
                foreach (var r in selector(s,index++))
                {
                    yield return r;
                }
            }
        }

        public static IEnumerable<TResult> FakeSelectMany<TSource, TCollection, TResult>(
            this IEnumerable<TSource> source,
            Func<TSource, IEnumerable<TCollection>> collectionSelector,
            Func<TSource, TCollection, TResult> resultSelector)
        {
            foreach (var s in source)
            {
                foreach (var c in collectionSelector(s))
                {
                    yield return resultSelector(s, c);
                }
            }
        }

        public static IEnumerable<TResult> FakeSelectMany<TSource, TCollection, TResult>(
            this IEnumerable<TSource> source,
            Func<TSource, int, IEnumerable<TCollection>> collectionSelector,
            Func<TSource, TCollection, TResult> resultSelector)
        {
            int index = 0;
            foreach (var s in source)
            {
                foreach (var c in collectionSelector(s,index++))
                {
                    yield return resultSelector(s, c);
                }
            }
        }
    }

        我们来试试效果,发现用起来是完全一样的感觉:

            List<Teacher> teachers = new List<Teacher> 
            {
                new Teacher("a",new List<Student>{ new Student(100),new Student(90),new Student(30) }),
                new Teacher("b",new List<Student>{ new Student(100),new Student(90),new Student(60) }),
                new Teacher("c",new List<Student>{ new Student(100),new Student(90),new Student(40) }),
                new Teacher("d",new List<Student>{ new Student(100),new Student(90),new Student(60) }),
                new Teacher("e",new List<Student>{ new Student(100),new Student(90),new Student(50) }),
                new Teacher("f",new List<Student>{ new Student(100),new Student(90),new Student(60) }),
                new Teacher("g",new List<Student>{ new Student(100),new Student(90),new Student(60) })
            };

            var list1 = teachers.SelectMany(t => t.Students).Where(s => s.Score < 60).ToList();
            var list2 = teachers.FakeSelectMany(t => t.Students).Where(s => s.Score < 60).ToList();

            var list3 = teachers.SelectMany(
                t => t.Students,
                (t, s) => new { t.Name, s.Score })
                .Where(n => n.Score < 60).ToList();
            var list4 = teachers.FakeSelectMany(
                t => t.Students,
                (t, s) => new { t.Name, s.Score })
                .Where(n => n.Score < 60).ToList();

        是不是有种微软就是个骗子,原来这么简单的感觉?其实也没那么简单,在完成上述代码之后,我又用ILSpy去看了微软SelectMany的实现,发现主要有以下3点区别:

        1.我没有对传入的参数做任何的有效性检测,这在平时做应用开发时可能不是大问题,但如果是写一个通用类库供他人使用是绝对不能缺少的:

    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }
    if (selector == null)
    {
        throw Error.ArgumentNull("selector");
    }

        2.存在第二个int index参数的情况下,我们没有对index进行溢出的检测:

    int num = -1;
    checked
    {
        foreach (TSource current in source)
        {
            num++;
            foreach (TResult current2 in selector(current, num))
            {
                yield return current2;
            }
        }
        yield break;
    }

        3.第三点就是上面的这个yield break,说实话我没有搞清楚为什么这里需要加。还望各位给我指点迷津。

        最后感谢韦恩卑鄙,没有这个胖胖就没有这篇文章。哈哈。

        本篇相关代码

原文地址:https://www.cnblogs.com/manupstairs/p/2793384.html