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

eoxn13cs  于 12个月前  发布在  其他
关注(0)|答案(6)|浏览(111)
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还贵,但是它是吗?

bqjvbblv

bqjvbblv1#

唯一的区别是List.GetRangeTake(n).ToList()更有效,因为它已经知道新列表的大小,而LINQ方法不知道它的大小。
所以ToList枚举序列,并使用加倍算法连续增加支持数组将项目添加到新列表中。List.GetRange可以预先创建具有正确初始大小的正确列表,然后使用Array.Copy将源列表的子集复制到新列表[source]中。

wj8zmpe1

wj8zmpe12#

它要快得多。看看这个:

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秒
List.Take().ToList():3.625秒

tag5nh1u

tag5nh1u3#

下面是**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**实现

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便宜多少

zzzyeukh

zzzyeukh4#

List.Take(100).ToList(),如果不知道列表中的元素个数,会更合适,如果小于100,只取可用的元素。这样使用更灵活。

另一方面,**List.GetRange(0,100)**假设列表中的元素数量大于100。但您会得到此错误 *
偏移量和长度超出数组的界限,或者计数大于从索引到源集合末尾的元素数

  • .如果元素的数量小于指定的范围。

对我来说,我会说List.Take(100).ToList()通用,因为它不限制使用。

pxiryf3j

pxiryf3j5#

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总是更容易,因为它不会导致任何未知的bug。
GetRange也很高效。

dffbzjpn

dffbzjpn6#

我会补充说,如果你需要在Take的结果上调用ToList,它比GetRange快得多。
从Kendrzu的例子中删除ToList,你会得到:

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);
}

Console.WriteLine(stopwatch.Elapsed);

字符串
经过时间:
List.GetRange():0.074s
List.Take():0.032s
当您可能不需要在Take的结果上调用ToList时的示例:

List<int> myList = new List<int>() { 1, 2, 3, 4 };

List<int> takeList = myList
    .Take(3)
    .Select(x => x + 2)
    .ToList();

List<int> getRangeList = myList
    .GetRange(0, 3)
    .Select(x => x + 2)
    .ToList();


无论我使用Take还是GetRange,我都需要调用ToList,所以所花费的时间不会影响我在TakeGetRange之间的决定。

相关问题