C# Linq源码分析之Take(五)

这篇具有很好参考价值的文章主要介绍了C# Linq源码分析之Take(五)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

概要

本文在C# Linq源码分析之Take(四)的基础上继续从源码角度分析Take的优化方法,主要分析Where.Select.Take的使用案例。

Where.Select.Take的案例分析

该场景模拟我们显示中将EF中与数据库关联的对象进行过滤,然后转换成Web前端需要的对象,并分页的情况。

 studentList.Where(x => x.MathResult >= 90).Select(x => new {
                x.Name,
                x.MathResult
            }).Take(3).ToList().ForEach(x=>Console.WriteLine(x.Name + x.MathResult));

找到数学90分以上的学生,获取学生的姓名和数学成绩,每次只取前三个学生。并将学生信息打印。Student类的代码请见附录。

源码流程分析

C# Linq源码分析之Take(五),c#,linq,开发语言
第一步进入Where方法,返回WhereListIterator对象;
第二步进入Select方法,将Where和Select两个操作合并,返回WhereSelectListIterator对象;
第三步进入Take方法,调用takeIterator方法;由于人WhereSelectListIterator并没有实现IPartition接口和IList接口,所以无法再进行操作合并,只能返回EnumerablePartition对象。

private static IEnumerable<TSource> takeIterator<TSource>(IEnumerable<TSource> source, int count)
 {
     Debug.Assert(count > 0);
     return
        source is IPartition<TSource> partition ? partition.Take(count) :
        source is IList<TSource> sourceList ? new ListPartition<TSource>(sourceList, 0, count - 1) :
        new EnumerablePartition<TSource>(source, 0, count - 1);
 }

第四步进入ToList方法

  public static List<TSource> ToList<TSource>(this IEnumerable<TSource> source)
 {
     if (source == null)
     {
         ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
     }
     return source is IIListProvider<TSource> listProvider ? listProvider.ToList() : new List<TSource>(source);
 }

此时的source是EnumerablePartition对象,它实现了IPartition接口,而IPartition接口继承了IIListProvider接口,所以可以调用自己的ToList方法;

public List<TSource> ToList()
{
    var list = new List<TSource>();

    using (IEnumerator<TSource> en = _source.GetEnumerator())
    {
        if (SkipBeforeFirst(en) && en.MoveNext())
        {
            int remaining = Limit - 1; // Max number of items left, not counting the current element.
            int comparand = HasLimit ? 0 : int.MinValue; // If we don't have an upper bound, have the comparison always return true.

            do
            {
                remaining--;
                list.Add(en.Current);
            }
            while (remaining >= comparand && en.MoveNext());
        }
    }

    return list;
}
  1. 定义迭代器en,此时的_source是WhereSelectListIterator对象;
  2. 该ToList方法同样支持Skip,所以要判断迭代器的起始位置是不是从第一个开始;
  3. 每次迭代,首先从WhereSelectListIterator迭代器中返回一个符合过滤条件,并完成Selector操作的元素,存入list,直到list中包含三个元素,返回执行结果。

虽然WhereSelectListIterator没有实现IPartition接口,不能实现一次迭代,完成全部操作,但是现有的流程性能并不差,因为WhereSelectListIterator迭代器本身已经合并了过滤和投影操作,而且并不需要遍历所有元素,只要找到3个符合条件的元素即可。

我认为如果代码需要用到Take方法,尽量把它放到Linq的最后。这样做的好处是前面的Linq操作并不需要遍历全部的序列元素,只要得到Take方法中需要的元素个数即可。

本文中涉及的源码请见附录,关于WhereSelectListIterator的合并优化操作,更多详细内容,请参考C# LINQ源码分析之Select

附录

Student类

public class Student {
    public string Id { get; set; }
    public string Name { get; set; }
    public string Classroom { get; set; }
    public int MathResult { get; set; }
}

IIListProvider接口

internal interface IIListProvider<TElement> : IEnumerable<TElement>
{
    TElement[] ToArray();
    List<TElement> ToList();
    int GetCount(bool onlyIfCheap);
}

IPartition接口

internal interface IPartition<TElement> : IIListProvider<TElement>
{

    IPartition<TElement> Skip(int count);

    IPartition<TElement> Take(int count);

    TElement? TryGetElementAt(int index, out bool found);

    TElement? TryGetFirst(out bool found);

    TElement? TryGetLast(out bool found);
}

EnumerablePartition类文章来源地址https://www.toymoban.com/news/detail-704849.html

private sealed class EnumerablePartition<TSource> : Iterator<TSource>, IPartition<TSource>
        {
            private readonly IEnumerable<TSource> _source;
            private readonly int _minIndexInclusive;
            private readonly int _maxIndexInclusive; // -1 if we want everything past _minIndexInclusive.
                                                     // If this is -1, it's impossible to set a limit on the count.
            private IEnumerator<TSource>? _enumerator;

            internal EnumerablePartition(IEnumerable<TSource> source, int minIndexInclusive, int maxIndexInclusive)
            {
                Debug.Assert(source != null);
                Debug.Assert(!(source is IList<TSource>), $"The caller needs to check for {nameof(IList<TSource>)}.");
                Debug.Assert(minIndexInclusive >= 0);
                Debug.Assert(maxIndexInclusive >= -1);
                // Note that although maxIndexInclusive can't grow, it can still be int.MaxValue.
                // We support partitioning enumerables with > 2B elements. For example, e.Skip(1).Take(int.MaxValue) should work.
                // But if it is int.MaxValue, then minIndexInclusive must != 0. Otherwise, our count may overflow.
                Debug.Assert(maxIndexInclusive == -1 || (maxIndexInclusive - minIndexInclusive < int.MaxValue), $"{nameof(Limit)} will overflow!");
                Debug.Assert(maxIndexInclusive == -1 || minIndexInclusive <= maxIndexInclusive);

                _source = source;
                _minIndexInclusive = minIndexInclusive;
                _maxIndexInclusive = maxIndexInclusive;
            }

            // If this is true (e.g. at least one Take call was made), then we have an upper bound
            // on how many elements we can have.
            private bool HasLimit => _maxIndexInclusive != -1;

            private int Limit => unchecked((_maxIndexInclusive + 1) - _minIndexInclusive); // This is that upper bound.

            public override Iterator<TSource> Clone() =>
                new EnumerablePartition<TSource>(_source, _minIndexInclusive, _maxIndexInclusive);

            public override void Dispose()
            {
                if (_enumerator != null)
                {
                    _enumerator.Dispose();
                    _enumerator = null;
                }

                base.Dispose();
            }

            public int GetCount(bool onlyIfCheap)
            {
                if (onlyIfCheap)
                {
                    return -1;
                }

                if (!HasLimit)
                {
                    // If HasLimit is false, we contain everything past _minIndexInclusive.
                    // Therefore, we have to iterate the whole enumerable.
                    //return Math.Max(_source.Count()- _minIndexInclusive, 0);
                    return 0;
                }

                using (IEnumerator<TSource> en = _source.GetEnumerator())
                {
                    // We only want to iterate up to _maxIndexInclusive + 1.
                    // Past that, we know the enumerable will be able to fit this partition,
                    // so the count will just be _maxIndexInclusive + 1 - _minIndexInclusive.

                    // Note that it is possible for _maxIndexInclusive to be int.MaxValue here,
                    // so + 1 may result in signed integer overflow. We need to handle this.
                    // At the same time, however, we are guaranteed that our max count can fit
                    // in an int because if that is true, then _minIndexInclusive must > 0.

                    uint count = SkipAndCount((uint)_maxIndexInclusive + 1, en);
                    Debug.Assert(count != (uint)int.MaxValue + 1 || _minIndexInclusive > 0, "Our return value will be incorrect.");
                    return Math.Max((int)count - _minIndexInclusive, 0);
                }

            }

            public override bool MoveNext()
            {
                // Cases where GetEnumerator has not been called or Dispose has already
                // been called need to be handled explicitly, due to the default: clause.
                int taken = _state - 3;
                if (taken < -2)
                {
                    Dispose();
                    return false;
                }

                switch (_state)
                {
                    case 1:
                        _enumerator = _source.GetEnumerator();
                        _state = 2;
                        goto case 2;
                    case 2:
                        Debug.Assert(_enumerator != null);
                        if (!SkipBeforeFirst(_enumerator))
                        {
                            // Reached the end before we finished skipping.
                            break;
                        }

                        _state = 3;
                        goto default;
                    default:
                        Debug.Assert(_enumerator != null);
                        if ((!HasLimit || taken < Limit) && _enumerator.MoveNext())
                        {
                            if (HasLimit)
                            {
                                // If we are taking an unknown number of elements, it's important not to increment _state.
                                // _state - 3 may eventually end up overflowing & we'll hit the Dispose branch even though
                                // we haven't finished enumerating.
                                _state++;
                            }
                            _current = _enumerator.Current;
                            return true;
                        }

                        break;
                }

                Dispose();
                return false;
            }

            public override IEnumerable<TResult> Select<TResult>(Func<TSource, TResult> selector) =>
                new SelectIPartitionIterator<TSource, TResult>(this, selector);

            public IPartition<TSource> Skip(int count)
            {
                int minIndex = unchecked(_minIndexInclusive + count);

                if (!HasLimit)
                {
                    if (minIndex < 0)
                    {
                        // If we don't know our max count and minIndex can no longer fit in a positive int,
                        // then we will need to wrap ourselves in another iterator.
                        // This can happen, for example, during e.Skip(int.MaxValue).Skip(int.MaxValue).
                        return new EnumerablePartition<TSource>(this, count, -1);
                    }
                }
                else if ((uint)minIndex > (uint)_maxIndexInclusive)
                {
                    // If minIndex overflows and we have an upper bound, we will go down this branch.
                    // We know our upper bound must be smaller than minIndex, since our upper bound fits in an int.
                    // This branch should not be taken if we don't have a bound.
                    return EmptyPartition<TSource>.Instance;
                }

                Debug.Assert(minIndex >= 0, $"We should have taken care of all cases when {nameof(minIndex)} overflows.");
                return new EnumerablePartition<TSource>(_source, minIndex, _maxIndexInclusive);
            }

            public IPartition<TSource> Take(int count)
            {
                int maxIndex = unchecked(_minIndexInclusive + count - 1);
                if (!HasLimit)
                {
                    if (maxIndex < 0)
                    {
                        // If we don't know our max count and maxIndex can no longer fit in a positive int,
                        // then we will need to wrap ourselves in another iterator.
                        // Note that although maxIndex may be too large, the difference between it and
                        // _minIndexInclusive (which is count - 1) must fit in an int.
                        // Example: e.Skip(50).Take(int.MaxValue).

                        return new EnumerablePartition<TSource>(this, 0, count - 1);
                    }
                }
                else if (unchecked((uint)maxIndex >= (uint)_maxIndexInclusive))
                {
                    // If we don't know our max count, we can't go down this branch.
                    // It's always possible for us to contain more than count items, as the rest
                    // of the enumerable past _minIndexInclusive can be arbitrarily long.
                    return this;
                }

                Debug.Assert(maxIndex >= 0, $"We should have taken care of all cases when {nameof(maxIndex)} overflows.");
                return new EnumerablePartition<TSource>(_source, _minIndexInclusive, maxIndex);
            }

            public TSource? TryGetElementAt(int index, out bool found)
            {
                // If the index is negative or >= our max count, return early.
                if (index >= 0 && (!HasLimit || index < Limit))
                {
                    using (IEnumerator<TSource> en = _source.GetEnumerator())
                    {
                        Debug.Assert(_minIndexInclusive + index >= 0, $"Adding {nameof(index)} caused {nameof(_minIndexInclusive)} to overflow.");

                        if (SkipBefore(_minIndexInclusive + index, en) && en.MoveNext())
                        {
                            found = true;
                            return en.Current;
                        }
                    }
                }

                found = false;
                return default;
            }

            public TSource? TryGetFirst(out bool found)
            {
                using (IEnumerator<TSource> en = _source.GetEnumerator())
                {
                    if (SkipBeforeFirst(en) && en.MoveNext())
                    {
                        found = true;
                        return en.Current;
                    }
                }

                found = false;
                return default;
            }

            public TSource? TryGetLast(out bool found)
            {
                using (IEnumerator<TSource> en = _source.GetEnumerator())
                {
                    if (SkipBeforeFirst(en) && en.MoveNext())
                    {
                        int remaining = Limit - 1; // Max number of items left, not counting the current element.
                        int comparand = HasLimit ? 0 : int.MinValue; // If we don't have an upper bound, have the comparison always return true.
                        TSource result;

                        do
                        {
                            remaining--;
                            result = en.Current;
                        }
                        while (remaining >= comparand && en.MoveNext());

                        found = true;
                        return result;
                    }
                }

                found = false;
                return default;
            }

   

            public List<TSource> ToList()
            {
                var list = new List<TSource>();

                using (IEnumerator<TSource> en = _source.GetEnumerator())
                {
                    if (SkipBeforeFirst(en) && en.MoveNext())
                    {
                        int remaining = Limit - 1; // Max number of items left, not counting the current element.
                        int comparand = HasLimit ? 0 : int.MinValue; // If we don't have an upper bound, have the comparison always return true.

                        do
                        {
                            remaining--;
                            list.Add(en.Current);
                        }
                        while (remaining >= comparand && en.MoveNext());
                    }
                }

                return list;
            }

            private bool SkipBeforeFirst(IEnumerator<TSource> en) => SkipBefore(_minIndexInclusive, en);

            private static bool SkipBefore(int index, IEnumerator<TSource> en) => SkipAndCount(index, en) == index;

            private static int SkipAndCount(int index, IEnumerator<TSource> en)
            {
                Debug.Assert(index >= 0);
                return (int)SkipAndCount((uint)index, en);
            }

            private static uint SkipAndCount(uint index, IEnumerator<TSource> en)
            {
                Debug.Assert(en != null);

                for (uint i = 0; i < index; i++)
                {
                    if (!en.MoveNext())
                    {
                        return i;
                    }
                }

                return index;
            }

            public TSource[] ToArray()
            {
                throw new NotImplementedException();
            }
        }

到了这里,关于C# Linq源码分析之Take(五)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C# Linq 详解三

    目录 概述 十三、Sum / Min / Max / Average 十四、Distinct 十五、Concat 十六、Join 十七、ToList  十八、ToArray 十九、ToDictionary C# Linq 文档(一) 1.Where,2.Select,3.GroupBy,4.First / FirstOrDefault,5.Last / LastOrDefault C# Linq 文档(二) 1.OrderBy ,2.OrderByDescending,3.Skip,4.Take,5.Any,6.All C# Linq 文

    2024年02月16日
    浏览(32)
  • C# Linq 详解一

    目录 一、概述 二、Where 三、Select 四、GroupBy 五、First / FirstOrDefault 六、Last / LastOrDefault C# Linq 文档(一) 1.Where,2.Select,3.GroupBy,4.First / FirstOrDefault,5.Last / LastOrDefault C# Linq 文档(二) 1.OrderBy, 2.OrderByDescending,3.Skip,4.Take,5.Any,6.All C# Linq 文档(三) 1.Sum / Min / Max / Av

    2024年02月16日
    浏览(60)
  • C# LINQ,SQL

    C#中的LINQ和SQL都是用于查询数据的工具,但它们有以下异同点: 异同点: 1. LINQ和SQL都可以用于查询数据,但LINQ是在C#中使用的语言集成查询,而SQL是一种独立的查询语言。 2. LINQ和SQL都支持基本的查询操作,如选择、过滤、排序和分组。 3. LINQ和SQL都支持连接多个表,但LI

    2024年02月10日
    浏览(35)
  • C# Linq 详解二

    目录 概述 七、OrderBy  八、OrderByDescending 九、Skip 十、Take 十一、Any 十二、All C# Linq 文档(一) 1.Where,2.Select,3.GroupBy,4.First / FirstOrDefault,5.Last / LastOrDefault C# Linq 文档(二) 1.OrderBy ,2.OrderByDescending,3.Skip,4.Take,5.Any,6.All C# Linq 文档(三) 1.Sum / Min / Max / Average,2.Dist

    2024年02月16日
    浏览(36)
  • C# Lambda,LINQ

    Lambda表达式和LINQ语句都是C#中用于查询和筛选数据的工具,但它们有一些异同点。 异同点: 1. 语法不同: Lambda表达式是一种匿名函数 ,它可以用于创建委托或表达式树。而LINQ语句是一种查询语句,它使用特定的和语法来查询数据。 2. 功能不同: Lambda表达式可以用于

    2024年02月09日
    浏览(320)
  • C# Linq 详解四

    目录 概述 二十、SelectMany 二十一、Aggregate 二十二、DistinctBy 二十三、Reverse 二十四、SequenceEqual 二十五、Zip 二十六、SkipWhile  二十七、TakeWhile C# Linq 文档(一) 1.Where,2.Select,3.GroupBy,4.First / FirstOrDefault,5.Last / LastOrDefault C# Linq 文档(二) 1.OrderBy ,2.OrderByDescending,3.Skip,

    2024年02月16日
    浏览(35)
  • C#中的LINQ

    一:什么是LINQ LINQ代表语言集成查询,是.net框架的扩展,它允许我们用SQL查询数据库的方式来查询数据的集合 二:LINQ延迟查询的特性 延迟查询是指查询操作并不是在定义的时候执行,而是在遍历集合中的数据时才执行 因为作为yield迭代器的主体,只有使用foreach遍历执行到

    2023年04月09日
    浏览(43)
  • C#中LINQ的使用知多少?LINQ常功能整理,实例源代码解析

      LINQ(Language-Integrated Query)是C#语言中的一个强大的查询技术,它提供了一种统一的查询语法,可以用于查询和操作各种数据源,包括集合、数据库、XML等。下面详细描述了LINQ的20个功能点,并提供了相应的源代码。 查询集合中的所有元素: 使用条件过滤集合中的元素:

    2024年02月05日
    浏览(53)
  • C#高级编程 Linq介绍

    本文对C#高级编程部分的Linq查询进行介绍 目录 一、Linq起源 二 LINQ 操作组成 2.1数据源 2.2查询 3.3查询执行

    2024年02月11日
    浏览(43)
  • 【C# 基础精讲】LINQ 基础

    LINQ(Language Integrated Query)是一项强大的C#语言特性,它使数据查询和操作变得更加简洁、灵活和可读性强。通过使用LINQ,您可以使用类似SQL的语法来查询各种数据源,如集合、数组、数据库等。本文将介绍LINQ的基础概念、常见的LINQ操作和示例,以及如何在C#中利用LINQ进行数

    2024年02月12日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包