出于无聊,我决定使用IEnumerable从头开始编写List的实现。我遇到了一些我真的不知道如何解决的问题:
最后但并非最不重要的是,缩小阵列的标准做法是什么?我确信,升级的最佳解决方案之一是将当前长度增加2倍;你何时何时缩小规模? Per Remove / RemoveAt或当前使用的长度%2?
这是我到目前为止所做的:
public class List<T> : IEnumerable<T>
{
T[] list = new T[32];
int current;
public void Add(T item)
{
if (current + 1 > list.Length)
{
T[] temp = new T[list.Length * 2];
Array.Copy(list, temp, list.Length);
list = temp;
}
list[current] = item;
current++;
}
public void Remove(T item)
{
for (int i = 0; i < list.Length; i++)
if (list[i].Equals(item))
list[i] = default(T);
}
public void RemoveAt(int index)
{
list[index] = default(T);
}
public IEnumerator<T> GetEnumerator()
{
foreach (T item in list)
if (item != null && !item.Equals(default(T)))
yield return item;
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
foreach (T item in list)
if (item != null && !item.Equals(default(T)))
yield return item;
}
}
提前致谢。
答案 0 :(得分:5)
嗯,对于初学者,您的Remove
和RemoveAt
方法不会实现与List<T>
相同的行为。 List<T>
的大小会减少1,而List
的大小会保持不变。您应该将较高索引的值从已删除的对象转移到一个较低的索引。
此外,GetEnumerator
将迭代数组中的所有项目,无论值是什么。
我相信这将解决您遇到的所有问题。如果有人在列表中添加了default(T)
,那么无论default(T)
是T
,还是0或类 - ,int
都会再次退出输入null
。
最后,在缩小规模时:一些可增长的阵列实现合理化,如果阵列变得如此之大,那么再次获得那么大的可能性比平常更大。因此,他们特别避免缩小规模。
答案 1 :(得分:4)
您遇到的关键问题是维护内部数组以及删除操作。 List<T>
内部不支持部分数组。这并不意味着你不能,但这样做要复杂得多。要完全模仿List<T>
,你要保留一个数组和一个字段,以获得实际使用的数组中元素的数量(列表长度,等于或小于数组长度)。
添加很简单,就像你一样在最后添加一个元素。
删除更复杂。如果要从末尾删除元素,请将结束元素设置为default(T)
并更改列表长度。如果要从开头或中间删除元素,则需要移动数组的内容并将最后一个设置为default(T)
。我们将最后一个元素设置为default(T)
的原因是清除引用,而不是我们可以判断它是否“正在使用”。我们知道它是否在“使用中”基于数组中的位置和我们存储的列表长度。
实现的另一个关键是枚举器。您希望遍历第一个元素,直到达到列表长度。不要跳过空值。
这不是一个完整的实现,但应该是你开始的方法的正确实现。
不过,我不同意我确信,升级的最佳解决方案是将当前长度增加2倍
这是List<T>
的默认行为,但它不是所有情况下的最佳解决方案。这正是List<T>
允许您指定容量的原因。如果您从源中加载列表并知道要添加的项目数,则可以预先初始化列表的容量以减少副本数。同样,如果您创建的数百或数千个列表大于默认大小或可能更大,那么将列表预初始化为相同大小对内存利用率可能有好处。这样,它们分配和释放的内存将是相同的连续块,并且可以更有效地分配和重新分配。例如,我们有一个报告计算引擎,可以为每次运行创建大约300,000个列表,其中许多运行一秒钟。我们知道列表总是每个都有几百个项目,因此我们将它们预先初始化为1024个容量。这不仅仅是大多数需要,但由于它们的长度都相同,而且它们的创建和处理速度非常快,因此可以使内存重用变得高效。
public class MyList<T> : IEnumerable<T>
{
T[] list = new T[32];
int listLength;
public void Add(T item)
{
if (listLength + 1 > list.Length)
{
T[] temp = new T[list.Length * 2];
Array.Copy(list, temp, list.Length);
list = temp;
}
list[listLength] = item;
listLength++;
}
public void Remove(T item)
{
for (int i = 0; i < list.Length; i++)
if (list[i].Equals(item))
{
RemoveAt(i);
return;
}
}
public void RemoveAt(int index)
{
if (index < 0 || index >= listLength)
{
throw new ArgumentException("'index' must be between 0 and list length.");
}
if (index == listLength - 1)
{
list[index] = default(T);
listLength = index;
return;
}
// need to shift the list
Array.Copy(list, index + 1, list, index, listLength - index + 1);
listLength--;
list[listLength] = default(T);
}
public IEnumerator<T> GetEnumerator()
{
for (int i = 0; i < listLength; i++)
{
yield return list[i];
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}