使用IEnumerable <t>从头开始编写List <t>的实现</t> </t>

时间:2013-08-28 01:00:33

标签: c# generics

出于无聊,我决定使用IEnumerable从头开始编写List的实现。我遇到了一些我真的不知道如何解决的问题:

  1. 当索引为空或设置为默认值(T)时,如何调整通用数组(T [])的大小?
  2. 由于你不能将T归零,你如何克服数值原语问题,默认值为0?
  3. 如果无法对#2做任何事情,那么在使用数值数据类型时如何停止产生返回0的GetEnumerator()方法呢?
  4. 最后但并非最不重要的是,缩小阵列的标准做法是什么?我确信,升级的最佳解决方案之一是将当前长度增加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;
        }
    }
    

    提前致谢。

2 个答案:

答案 0 :(得分:5)

嗯,对于初学者,您的RemoveRemoveAt方法不会实现与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();
        }
    }