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#。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C# Enumerable 是一个非常有用的工具,它提供了一种简洁且灵活的方式来处理集合类型的数据。它是一个泛型接口,定义了一组方法,使得我们可以对集合进行迭代、筛选、转换等操作。 Enumerable 接口定义了一些常用的操作方法,包括: - `Where`:根据指定的条件筛选集合中的元素。 - `Select`:对集合中的每个元素进行转换,并返回一个的序列。 - `OrderBy` 和 `OrderByDescending`:根据指定的条件对集合进行排序。 - `Skip` 和 `Take`:跳过指定数量的元素或者获取指定数量的元素。 - `Aggregate`:使用指定的聚合函数对集合中的元素进行累积计算。 - `Join` 和 `GroupJoin`:根据指定的条件将两个集合进行连接。 通过使用 Enumerable,我们可以更加简洁和优雅地操作集合,而无需手动编写循环和条件判断逻辑。同时,Enumerable 也提供了延迟执行的特性,这意味着我们可以通过链式调用多个方法来构建复杂的查询表达式,并在需要时进行计算。 下面是一个简单的示例,展示了如何使用 Enumerable 进行筛选和转换操作: ```csharp var numbers = new List<int> { 1, 2, 3, 4, 5 }; var evenSquares = numbers .Where(n => n % 2 == 0) // 筛选偶数 .Select(n => n * n); // 平方 foreach (var number in evenSquares) { Console.WriteLine(number); } ``` 以上代码会输出 4 和 16,分别是筛选出的偶数的平方。这只是 Enumerable 的一小部分功能,你可以根据具体需求来选择适合的方法来处理集合数据。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值