C# 列表上的Select()是否会丢失集合的大小?
在下面的代码中,C# 列表上的Select()是否会丢失集合的大小?,c#,linq,C#,Linq,在下面的代码中,Select()方法是否足够聪明,可以将列表的大小保持在内部某个位置,从而使ToArray()方法变得便宜 List<Thing> bigList = someBigList; var bigArray = bigList.Select(t => t.SomeField).ToArray(); List bigList=someBigList; var bigArray=bigList.Select(t=>t.SomeField.ToArray(); 这很容易
Select()
方法是否足够聪明,可以将列表的大小保持在内部某个位置,从而使ToArray()
方法变得便宜
List<Thing> bigList = someBigList;
var bigArray = bigList.Select(t => t.SomeField).ToArray();
List bigList=someBigList;
var bigArray=bigList.Select(t=>t.SomeField.ToArray();
这很容易检查,无需查看实现。只需创建一个实现IList
的类,并在Count
属性中放置一个跟踪:
class MyList<T> : IList<T>
{
private readonly IList<T> _list = new List<T>();
public IEnumerator<T> GetEnumerator()
{
return _list.GetEnumerator();
}
public void Add(T item)
{
_list.Add(item);
}
public void Clear()
{
_list.Clear();
}
public bool Contains(T item)
{
return _list.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
_list.CopyTo(array, arrayIndex);
}
public bool Remove(T item)
{
return _list.Remove(item);
}
public int Count
{
get
{
Console.WriteLine ("Count accessed");
return _list.Count;
}
}
public bool IsReadOnly
{
get { return _list.IsReadOnly; }
}
public int IndexOf(T item)
{
return _list.IndexOf(item);
}
public void Insert(int index, T item)
{
_list.Insert(index, item);
}
public void RemoveAt(int index)
{
_list.RemoveAt(index);
}
public T this[int index]
{
get { return _list[index]; }
set { _list[index] = value; }
}
#region Implementation of IEnumerable
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
}
但是它没有打印任何东西,所以不,它没有记录计数。当然,可能会有特定于列表的优化,但似乎不太可能…不,目前没有(至少是.NET实现)。从MS参考源中,可枚举.ToArray
实现为
public static TSource[] ToArray<TSource>(this IEnumerable<TSource> source) {
if (source == null) throw Error.ArgumentNull("source");
return new Buffer<TSource>(source).ToArray();
}
publicstatictsource[]ToArray(此IEnumerable源){
if(source==null)抛出错误.ArgumentNull(“source”);
返回新的缓冲区(source.ToArray();
}
Buffer
根据需要迭代和调整大小,在构建时创建源序列的副本(以数组形式);如果source
是一个ICollection
,但的结果是可枚举的,则它有一个特殊的“快速路径”。毫不奇怪,Select
不会实现该接口
尽管如此,除了纯粹的好奇,我认为这个结果没有任何意义。首先,实施可能会在未来的任何时候发生变化(即使快速的成本效益分析不会发现这一点)。在任何情况下,您最多会遭受O(logN)重新分配。对于小N,重新分配将不会明显。对于较大的N,在集合上迭代所花费的时间量将是O(N),因此将很容易占据主导地位。当您将选择运算符应用于可枚举序列时,它将创建以下迭代器之一:
其中选择ArrayIterator
WhereSelectListIterator
WhereSelectEnumerableIterator
如果是列表
,将在其中创建SelectListIterator
迭代器。它使用列表的迭代器对列表进行迭代,并应用谓词和选择器。这是一个MoveNext
方法实现:
while (this.enumerator.MoveNext())
{
TSource current = this.enumerator.Current;
if ((this.predicate == null) || this.predicate(current))
{
base.current = this.selector(current);
return true;
}
}
如您所见,它不保留有关匹配谓词的项数的信息,因此,它不知道过滤序列中的项目数。BCL源代码可从某些Microsoft服务器获得,供您阅读此类内容-当您导航到该类时,ReSharper可以自动下载并显示它们。Mono检查ICollection,尚未查看是否通过Select,但事实似乎并非如此@Dykam Mono是一个无尘室实现,不是吗?这将使其无法对公司的业绩进行陈述。NET@Dykam,这是Mono实现,.NET实现不一定相同…@Ankush:wellList.Count()
不需要枚举,因此当您执行List.ToArray()
时,它只分配一次数组。IEnumerable不能做到这一点,因此它需要一直重新分配结果,这是非常昂贵的。我想知道List().Select()是否创建了一个跟踪集合大小的对象我喜欢你解释了你是如何得出答案的。@ThomasLevesque:很好的解决方法!谢谢
while (this.enumerator.MoveNext())
{
TSource current = this.enumerator.Current;
if ((this.predicate == null) || this.predicate(current))
{
base.current = this.selector(current);
return true;
}
}