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.GetRange
比Take(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);
}
}
}
}
您可以立即注意到GetRange
比Take
便宜多少
list. take (100). tolist (),如果您不知道列表中的元素数量,则更合适。如果小于100,就只取可用的。
另一方面, list . getrange (0,100)假设列表中的元素数量大于100。然而,你会得到这个错误***
偏移量和长度超出了数组的范围,或者计数更大取从索引到源端元素的个数收集
* * *。
对于我来说,我会说List.Take(100). tolist ()是更通用的,因为它不限制使用
在Take
和GetRange
之间有一个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也是有效的。