list.Take(100).ToList() vs. list.GetRange(0,100)

本文关键字:list GetRange ToList Take vs | 更新日期: 2023-09-27 18:13:38

List<AttendeeInfo> attendees = new List<AttendeeInfo>();
foreach ...
// Error: "There are too many target users in the email address array"
// for more than 100 attendees. So take the first 100 attendees only.
if(attendees.Count > 100) attendees = attendees.GetRange(0,100);
// or
if(attendees.Count > 100) attendees = attendees.Take(100).ToList();

由于我处理的列表总是超过100个,并且总是取前100个,因此最明显的差异(求值策略,跳过的可能性,抛出错误)并不真正有趣。

但是也许你可以解释一下"在源列表中创建一个元素范围的浅拷贝"到底是什么意思。听起来真的很贵,比《Take》贵,但真的是这样吗?

list.Take(100).ToList() vs. list.GetRange(0,100)

唯一的区别是List.GetRangeTake(n).ToList()更有效,因为它已经知道新列表的大小,而LINQ方法不知道它的大小。

因此ToList枚举序列并将项目添加到一个新的列表中,并使用加倍算法连续增加后备数组。List.GetRange可以预先创建合适的初始大小的列表,然后使用Array.Copy将源列表的子集复制到新列表[source]中。

速度快多了。看看这个:

var list = Enumerable.Range(0, 1000).ToList();
var stopwatch = new Stopwatch();
stopwatch.Start();
for(var i=0; i<1000000; i++)
{
    var c = list.GetRange(0, 100);
}
Console.WriteLine(stopwatch.Elapsed);
stopwatch.Restart();
for (var i = 0; i < 1000000; i++)
{
     var c = list.Take(100).ToList();
}
Console.WriteLine(stopwatch.Elapsed);

运行时间:

List.GetRange(): 0.149 s

List.Take().ToList(): 3.625 s

以下是 GetRange 的实现:

public List<T> GetRange(int index, int count)
{
    if (index < 0)
    {
        ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
    }
    if (count < 0)
    {
        ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
    }
    if ((this._size - index) < count)
    {
        ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidOffLen);
    }
    List<T> list = new List<T>(count);
    Array.Copy(this._items, index, list._items, 0, count); // Implemented natively
    list._size = count;
    return list;
}

这是 Take Implementation

public static IEnumerable<TSource> Take<TSource>(this IEnumerable<TSource> source, int count)
{
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }
    return TakeIterator<TSource>(source, count);
}
private static IEnumerable<TSource> TakeIterator<TSource>(IEnumerable<TSource> source, int count)
{
    if (count > 0)
    {
        foreach (TSource iteratorVariable0 in source)
        {
            yield return iteratorVariable0;
            if (--count == 0)
            {
                break;
            }
        }
    }
}

加上ToList,简单地做:

public static List<TSource> ToList<TSource>(this IEnumerable<TSource> source)
{
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }
    return new List<TSource>(source);
}

List构造函数:

public List(IEnumerable<T> collection)
{
    if (collection == null)
    {
        ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection);
    }
    ICollection<T> is2 = collection as ICollection<T>;
    if (is2 != null)
    {
        int count = is2.Count;
        if (count == 0)
        {
            this._items = List<T>._emptyArray;
        }
        else
        {
            this._items = new T[count];
            is2.CopyTo(this._items, 0);
            this._size = count;
        }
    }
    else
    {
        this._size = 0;
        this._items = List<T>._emptyArray;
        using (IEnumerator<T> enumerator = collection.GetEnumerator())
        {
            while (enumerator.MoveNext())
            {
                this.Add(enumerator.Current);
            }
        }
    }
}

您可以立即注意到GetRangeTake便宜多少

list. take (100). tolist (),如果您不知道列表中的元素数量,则更合适。如果小于100,就只取可用的。

另一方面, list . getrange (0,100)假设列表中的元素数量大于100。然而,你会得到这个错误***

偏移量和长度超出了数组的范围,或者计数更大取从索引到源端元素的个数收集

* * *。

对于我来说,我会说List.Take(100). tolist ()是更通用的,因为它不限制使用

TakeGetRange之间有一个minor。Take会惰性地求值,直到你强制它求值到ToList()。这可以改变行为。

考虑伪代码。

List<int> myList = new List<int>() {1,2,3,4,5,6};
IEnumerable<int> otherList = myList.Take(3);  // {1,2,3};
myList.RemoveRange(0,3);
// myList = {4, 5, 6}
// otherList = {4, 5, 6}

现在将此示例更改为以下代码:

List<int> myList = new List<int>() {1,2,3,4,5,6};
IEnumerable<int> otherList = myList.Take(3).ToList();  // {1,2,3};
myList.RemoveRange(0,3);
// myList = {4, 5, 6}
// otherList = {1, 2, 3}

执行ToList()可以根据您接下来使用的操作改变Take或GetRange的行为。使用GetRange总是更容易,因为它不会导致任何未知的错误。

GetRange也是有效的。

相关文章: