乐趣区

关于c#:Enumerable-下又有新的扩展方法啦快来一起一睹为快吧

一:背景

1. 讲故事

前段时间将公司的一个我的项目从 4.5 降级到了 framework 4.8,编码的时候发现 Enumerable 中多了三个扩大办法:Append, Prepend, ToHashSet,想必玩过 jquery 的敌人一眼就能看出这三个办法的用处,这篇就和大家一起来聊聊这三个办法的底层源码实现,看有没有什么新货色能够挖出来。

二:Enumerable 下的新扩大办法

1. Append

看到这个我的第一印象就是 Add 办法,惋惜在 Enumerable 中并没有相似的办法,可能起初程序员在这块的呼声越来越高,C# 开发团队就补救了这个遗憾。

<1> 单条数据的追加

接下来我写一个小例子往汇合的尾部追加一条数据, 如下代码所示:


        static void Main(string[] args)
        {var arr = new int[2] {1, 2};

            var result = Enumerable.Append(arr, 3);

            foreach (var item in result)
            {Console.WriteLine(item);
            }
        }

逻辑还是十分清晰的,再来看看底层源码是怎么实现的。


public static IEnumerable<TSource> Append<TSource>(this IEnumerable<TSource> source, TSource element)
{if (source == null)
    {throw Error.ArgumentNull("source");
    }
    AppendPrependIterator<TSource> appendPrependIterator = source as AppendPrependIterator<TSource>;
    if (appendPrependIterator != null)
    {return appendPrependIterator.Append(element);
    }
    return new AppendPrepend1Iterator<TSource>(source, element, appending: true);
}


private class AppendPrepend1Iterator<TSource> : AppendPrependIterator<TSource>
{public AppendPrepend1Iterator(IEnumerable<TSource> source, TSource item, bool appending) : base(source)
    {
        _item = item;
        _appending = appending;
    }

    public override bool MoveNext()
    {switch (state)
        {
        case 1:
            state = 2;
            if (!_appending)
            {
                current = _item;
                return true;
            }
            goto case 2;
        case 2:
            GetSourceEnumerator();
            state = 3;
            goto case 3;
        case 3:
            if (LoadFromEnumerator())
            {return true;}
            if (_appending)
            {
                current = _item;
                return true;
            }
            break;
        }
        Dispose();
        return false;
    }

}

从下面的源码来看,这玩意做的还是挺简单的,继承关系顺次是:AppendPrepend1Iterator<TSource> -> AppendPrependIterator<TSource> -> Iterator<TSource>,这里大家要着重看一下 MoveNext() 外面的两个办法 GetSourceEnumerator() 和 LoadFromEnumerator(),如下代码所示:

能够看到,第一个办法用于获取 Array 这个数据源,上面这个办法用于遍历这个 Array,当 foreach 遍历完之后,执行 case 3 语句,也就是上面的 if 语句,将你追加的 3 迭代一下,如下图:

<2> 批量数据的追加

咱们晓得汇合的增加除了 Add 还有 AddRange,很遗憾,Enumerable 下并没有找到相似的 AppendRange 办法,那如果要实现 AppendRange 操作该怎么解决呢?哈哈,只能本人 foreach 迭代啦,如下代码:


        static void Main(string[] args)
        {var arr = new int[2] {1, 2};

            var arr2 = new int[3] {3, 4, 5};

            IEnumerable<int> collection = arr;

            foreach (var item in arr2)
            {collection = collection.Append(item);
            }
            foreach (var item in collection)
            {Console.WriteLine(item);
            }
        }

后果也是非常简单的,因为 IEnumerable 是非破坏性的操作,所以你须要在 Append 之后用类型给接住,接下来找一下底层源码。


public static IEnumerable<TSource> Append<TSource>(this IEnumerable<TSource> source, TSource element)
{if (source == null)
    {throw Error.ArgumentNull("source");
    }
    AppendPrependIterator<TSource> appendPrependIterator = source as AppendPrependIterator<TSource>;
    if (appendPrependIterator != null)
    {return appendPrependIterator.Append(element);
    }
    return new AppendPrepend1Iterator<TSource>(source, element, appending: true);
}

private class AppendPrepend1Iterator<TSource> : AppendPrependIterator<TSource>
{public override AppendPrependIterator<TSource> Append(TSource item)
    {if (_appending)
        {return new AppendPrependN<TSource>(_source, null, new SingleLinkedNode<TSource>(_item).Add(item), 0, 2);
        }
        return new AppendPrependN<TSource>(_source, new SingleLinkedNode<TSource>(_item), new SingleLinkedNode<TSource>(item), 1, 1);
    }
}

private class AppendPrependN<TSource> : AppendPrependIterator<TSource>
{public override AppendPrependIterator<TSource> Append(TSource item)
    {SingleLinkedNode<TSource> appended = (_appended != null) ? _appended.Add(item) : new SingleLinkedNode<TSource>(item);
        return new AppendPrependN<TSource>(_source, _prepended, appended, _prependCount, _appendCount + 1);
    }
}

从下面的代码能够看出,当你 Append 屡次的时候,实质上就是屡次调用 AppendPrependN<TSource>.Append(),而且在调用的过程中,始终将你后续增加的元素追加到 SingleLinkedNode 单链表中,这里要留神的是 Add 采纳的是 头插法,所以最初插入的元素会在队列头部,如下图:

如果你不信的话,我能够在 vs 调试中给您展现进去。

貌似说的有点啰嗦,最初大家察看一下 AppendPrependN<TSource>.MoveNext 的实现就能够了。

说了这么多,我想你应该明确了哈。

2. Prepend

实质上来说 Prepend 和 Append 是一对的,一个是在后面插入,一个是在前面插入,不要想歪了,如果你仔细的话,你会发现 Prepend 也是用了这三个类:AppendPrepend1Iterator<TSource>,AppendPrependIterator<TSource>,AppendPrependN<TSource> 以及 单链表 SingleLinkedNode<TSource>,这个就留给大家本人钻研了哈。

3. ToHashSet

我以前在全内存开发中会频繁的用到 HashSet,毕竟它的工夫复杂度是 O(1),而且在 Enumerable 中早就有了 ToList 和 ToDictionary,凭啥没有 ToHashSet,在以前只能将 source 塞到 HashSet 的构造函数中,如:new HashSet<int>(source),想想也是够奇葩的哈,而且我还想吐糟一下的是竟然到当初还没有 AddRange 批量增加办法,气人哈,接下来用 ILSpy 看一下这个扩大办法是如何实现的。

三:总结

总体来说这三个办法还是很实用的,我置信在后续的版本中 Enumerable 下的扩大办法还会越来越多,越来越人性化,人生苦短,我用 C#。

退出移动版