这是一个老问题,但我有两个解决方案。一是快速而肮脏的反射;另一个是实际回答问题的解决方案(设置大小而不是容量),同时仍然具有高性能,这里的答案都没有。
反射
这既快又脏,代码的作用应该很明显。如果你想加快速度,缓存 GetField 的结果,或者创建一个 DynamicMethod 来做:
public static void SetSize<T>(this List<T> l, int newSize) =>
l.GetType().GetField("_size", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(l, 10);
显然很多人会犹豫是否将这样的代码投入生产。
ICollection<T>
该解决方案基于这样一个事实,即构造函数List(IEnumerable<T> collection)
优化ICollection<T>
并立即将大小调整到正确的数量,而无需对其进行迭代。然后它调用集合CopyTo
来进行复制。
代码如下:
public List(IEnumerable<T> collection) {
....
ICollection<T> c = collection as ICollection<T>;
if (collection is ICollection<T> c)
{
int count = c.Count;
if (count == 0)
{
_items = s_emptyArray;
}
else {
_items = new T[count];
c.CopyTo(_items, 0);
_size = count;
}
}
因此,我们可以完全优化地将 List 预初始化为正确的大小,而无需任何额外的复制。
怎么会这样?通过创建一个ICollection<T>
除了返回 a 之外什么都不做的对象Count
。具体来说,我们不会实现任何CopyTo
唯一调用其他函数的东西。
private class SizeCollection<T> : ICollection<T>
{
public SizeCollection(int size) =>
Count = size;
public void Add(T i){}
public void Clear(){}
public bool Contains(T i)=>true;
public void CopyTo(T[]a, int i){}
public bool Remove(T i)=>true;
public int Count {get;}
public bool IsReadOnly=>true;
public IEnumerator<T> GetEnumerator()=>null;
IEnumerator IEnumerable.GetEnumerator()=>null;
}
public List<T> InitializedList<T>(int size) =>
new List<T>(new SizeCollection<T>(size));
理论上,我们可以对现有数组AddRange
/做同样的事情,这也解释了循环:InsertRange
ICollection<T>
Add
public void SetSize<T>(this List<T> l, int size)
{
if(size < l.Count)
l.RemoveRange(size, l.Count - size);
else
for(size -= l.Count; size > 0; size--)
l.Add(default(T));
}