美文网首页
c# MoreLinq 之 Append、Prepend

c# MoreLinq 之 Append、Prepend

作者: wwmin_ | 来源:发表于2021-02-22 23:07 被阅读0次

    前言

    本系列是对MoreLinq库的学习与总结,分析各个Api的实现方式及用法,也为能写出更高效的Linq打下基础。

    Append 向当前列表添加值,Prepend 在当前列表头部插入值

    void AppendTest()
    {
        var head = new[] { "first", "second" };
        var tail = "third";
        var whole = head.Append(tail);
        whole.Dump();//{ "first", "second","third" }
    }
    
    void PrependTest()
    {
        var head = new[] { 1, 2 };
        var whole = head.Prepend(0);
        whole.Dump();//0,1,2
    }
    
    //当数据大于4时Aggregate方法处理逻辑:concats = new T[item.ConcatCount];
    void AppendManyTest()
    {
        var a = Enumerable.Range(0, 5);
        var b = Enumerable.Range(0, 5);
        var res = a.Aggregate(b.AsEnumerable(), (xs, s) => xs.Append(s));
        res.Dump();//0,1,2,3,4,0,1,2,3,4
    }
    

    在列表尾部和头部插入值。

    Append、Prepend定义

            //Append定义
        public static IEnumerable<T> Append<T>(this IEnumerable<T> head, T tail)
        {
            if (head == null) throw new ArgumentNullException(nameof(head));
            return head is PendNode<T> node ? node.Concat(tail) : PendNode<T>.WithSource(head).Concat(tail);
        }
    
            //Prepend定义
        public static IEnumerable<TSource> Prepend<TSource>(this IEnumerable<TSource> source, TSource value)
        {
            if (source == null) throw new ArgumentNullException(nameof(source));
            return source is PendNode<TSource> node ? node.Prepend(value) : PendNode<TSource>.WithSource(source).Prepend(value);
        }
    

    这两个定义都涉及到PendNode<T>对象,该对象定义如下:

    public abstract class PendNode<T> : IEnumerable<T>
    {
        public static PendNode<T> WithSource(IEnumerable<T> source) => new Source(source);
    
        public PendNode<T> Prepend(T item) => new Item(item, isPrepend: true, next: this);
    
        public PendNode<T> Concat(T item) => new Item(item, isPrepend: false, next: this);
    
        sealed class Item : PendNode<T>
        {
            public T Value { get; }
            public bool IsPrepend { get; }
            public int ConcatCount { get; }
            public PendNode<T> Next { get; }
    
            public Item(T item, bool isPrepend, PendNode<T> next)
            {
                if (next == null) throw new ArgumentNullException(nameof(next));
                Value = item;
                IsPrepend = isPrepend;
                ConcatCount = next is Item nextItem ? nextItem.ConcatCount + (isPrepend ? 0 : 1) : 1;
                Next = next;
            }
        }
    
        sealed class Source : PendNode<T>
        {
            public IEnumerable<T> Value { get; }
            public Source(IEnumerable<T> source) => Value = source;
        }
    
        public IEnumerator<T> GetEnumerator()
        {
            var i = 0;
            T[]? concats = null;//Array for > 4 concatenations
            var concat1 = default(T);
            var concat2 = default(T);
            var concat3 = default(T);
            var concat4 = default(T);
    
            var current = this;
            for (; current is Item item; current = item.Next)
            {
                if (item.IsPrepend)
                {
                    yield return item.Value;
                }
                else
                {
                    if (concats == null)
                    {
                        if (i == 0 && item.ConcatCount > 4)
                        {
                            concats = new T[item.ConcatCount];
                        }
                        else
                        {
                            switch (i++)
                            {
                                case 0: concat1 = item.Value; break;
                                case 1: concat2 = item.Value; break;
                                case 2: concat3 = item.Value; break;
                                case 3: concat4 = item.Value; break;
                                default: throw new IndexOutOfRangeException();
                            }
                            continue;
                        }
                    }
                    concats[i++] = item.Value;
                }
            }
            var source = (Source)current;
            foreach (var item in source.Value)
            {
                yield return item;
            }
            if (concats == null)
            {
                if (i == 4) { yield return concat4!; i--; }
                if (i == 3) { yield return concat3!; i--; }
                if (i == 2) { yield return concat2!; i--; }
                if (i == 1) { yield return concat1!; i--; }
                yield break;
            }
            for (i--; i >= 0; i--)
            {
                yield return concats[i];
            }
        }
        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    }
    

    分析:

    1. 该对象定义了一个静态方法WithSource<T>,其将遍历对象包装成PendNode<T>对象new Source(source)。Source对象只做了一件事保存遍历对象并提供Value字段方式获取该值。
    2. 定义了Prepend方法和Concat方法,这两个方法都是返回一个Item对象,唯一的区别是传入的isPrepend是true还是false。其中Item对象定义了四个变量(T value , bool isPrepend,PendNode<T> next).
    3. 核心方法:GetEnumerator(),当是isPrepend时,先返回添加值,在遍历对象。当是append时先遍历对象在返回添加值。当添加的是数组时且长度大于4则新建T[],依次添加值,然后再返回值。
    4. void AppendManyTest()方法中测试时会看到concats[i++] = item.Value;是先后在前的,原因在于对象Item的构建上 public PendNode<T> Concat(T item) => new Item(item, isPrepend: false, next: this);后遍历的会将父级作为Next,故concats[i++] = item.Value; 值是从后到前的,最后在反遍历回来for (i--; i >= 0; i--) { yield return concats[i]; },即可得到正确值。

    本文作者:wwmin
    微信公众号: DotNet技术说
    本文链接:https://www.jianshu.com/p/737db6580d0b
    关于博主:评论和私信会在第一时间回复。或者[直接私信]我。
    版权声明:转载请注明出处!
    声援博主:如果您觉得文章对您有帮助,关注点赞, 您的鼓励是博主的最大动力!

    相关文章

      网友评论

          本文标题:c# MoreLinq 之 Append、Prepend

          本文链接:https://www.haomeiwen.com/subject/wnykfltx.html