C# 使用IEnumerable和数组制作的自定义堆栈?

C# 使用IEnumerable和数组制作的自定义堆栈?,c#,arrays,stack,ienumerable,C#,Arrays,Stack,Ienumerable,我一直在努力解决这个问题。 我试图使CustomStack像Stack一样工作,只实现PushT、Pop、Peek和Clear方法。我得到了这个代码,我认为它是正确的,但是输出只显示了一半的数字。我认为这与推送法有关,但我看不出它有什么问题 using System; using System.Collections.Generic; using System.Collections; using System.Linq; using System.Text; namespace Enumer

我一直在努力解决这个问题。 我试图使CustomStack像Stack一样工作,只实现PushT、Pop、Peek和Clear方法。我得到了这个代码,我认为它是正确的,但是输出只显示了一半的数字。我认为这与推送法有关,但我看不出它有什么问题

using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;

namespace Enumerator
{
    class Program
    {
        static void Main(string[] args)
        {
            CustomStack<int> collection = new CustomStack<int>();

            for (int i = 0; i < 30; i++)
            {
                collection.Push(i);
                Console.WriteLine(collection.Peek());
            }
            collection.Push(23);
            foreach (int x in collection)
            {
                Console.WriteLine(collection.Pop());
            }

            Console.WriteLine("current", collection.Peek());
            Console.ReadKey();
        }
    }

    public class CustomStack<T> : IEnumerable<T>
    {

        private T[] arr;
        private int count;

        public CustomStack()
        {
            count = 0;
            arr = new T[5];
        }


        public T Pop()
        {
            int popIndex = count;
            if (count > 0)
            {
                count--;
                return arr[popIndex];
            }
            else
            {
                return arr[count];
            }

        }

        public void Push(T item)
        {

            count++;
            if (count == arr.Length)
            {
                Array.Resize(ref arr, arr.Length + 1);
            }

            arr[count] = item;


        }

        public void Clear()
        {
            count = 0;

        }

        public T Peek()
        {
            return arr[count];
        }

        public int Count
        {
            get
            {
                return count;
            }
        }

        public IEnumerator<T> GetEnumerator()
        {
            return new MyEnumerator(this);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return new MyEnumerator(this);
        }

        public class MyEnumerator : IEnumerator<T>
        {
            private int position;
            private CustomStack<T> stack;

            public MyEnumerator(CustomStack<T> stack)
            {
                this.stack = stack;
                position = -1;
            }
            public void Dispose()
            {

            }
            public void Reset()
            {
                position = -1;
            }

            public bool MoveNext()
            {
                position++;
                return position < stack.Count;
            }

            Object IEnumerator.Current
            {
                get
                {
                    return stack.arr[position];
                }
            }
            public T Current
            {
                get
                {
                    return stack.arr[position];

                }
            }
        }
    }
}

您正在做一些您永远不能做的事情:您正在修改集合,同时使用枚举器对其进行迭代。foreach循环是分配枚举数的语法糖

IEnumerable的文档实际上表明,如果在枚举时修改了数据结构,那么像您这样的实现会抛出异常。用列表试试,你会发现;如果在foreach中枚举列表时添加或删除项目,列表将抛出

这就是你问题的原因;您的数据结构在被滥用时不是设计为1抛出,也不是设计为2在被滥用时表现良好,因此,当您滥用它时,它表现不好

我的建议是:如果你那样做会受伤,不要那样做。在枚举集合的循环中不要修改集合

相反,创建一个IsEmpty属性并编写循环:

while(!collection.IsEmpty)  
  Console.WriteLine(collection.Pop());
这样,您就不会在同时处理枚举数时修改集合

这里的具体问题是:每次通过循环,位置总是在增加。而且数量一直在减少。你说只有一半的物品被清点。好吧,解决它。如果您有十个项目,位置从零开始,并增加到大于计数,然后每次通过循环

position    count
 0           10
 1           9
 2           8
 3           7
 4           6
 5           5  
我们完成了,我们只列举了一半的项目

如果您想使集合在迭代过程中被修改时变得健壮,那么在推送或弹出堆栈时,位置必须改变。即使数量在变化,也不能每次都盲目增加。制定正确的行为是相当棘手的,这就是为什么文档建议您只需抛出

如果要使集合在枚举时被修改时引发异常,技巧是使对象具有名为版本号的int。每次推送或弹出集合时,请更改版本号。然后让迭代器在迭代开始时复制版本号;如果它检测到当前版本号与副本不同,则在枚举期间已修改集合,您可以引发集合修改异常


谢谢你提出的有趣的问题;我可能会在我的博客中将其作为一个例子,并且可能会看看我是否可以编写一个静态分析器来检测这种危险的修改。

您正在做一些您永远不能做的事情:您正在修改一个集合,而您正在使用枚举器对其进行迭代。foreach循环是分配枚举数的语法糖

IEnumerable的文档实际上表明,如果在枚举时修改了数据结构,那么像您这样的实现会抛出异常。用列表试试,你会发现;如果在foreach中枚举列表时添加或删除项目,列表将抛出

这就是你问题的原因;您的数据结构在被滥用时不是设计为1抛出,也不是设计为2在被滥用时表现良好,因此,当您滥用它时,它表现不好

我的建议是:如果你那样做会受伤,不要那样做。在枚举集合的循环中不要修改集合

相反,创建一个IsEmpty属性并编写循环:

while(!collection.IsEmpty)  
  Console.WriteLine(collection.Pop());
这样,您就不会在同时处理枚举数时修改集合

这里的具体问题是:每次通过循环,位置总是在增加。而且数量一直在减少。你说只有一半的物品被清点。好吧,解决它。如果您有十个项目,位置从零开始,并增加到大于计数,然后每次通过循环

position    count
 0           10
 1           9
 2           8
 3           7
 4           6
 5           5  
我们完成了,我们只列举了一半的项目

如果您想使集合在迭代过程中被修改时变得健壮,那么在推送或弹出堆栈时,位置必须改变。即使数量在变化,也不能每次都盲目增加。制定正确的行为是相当棘手的,这就是为什么文档建议您只需抛出

如果要使集合在枚举时被修改时引发异常,技巧是使对象具有名为版本号的int。每次推送或弹出集合时,请更改版本号。然后让迭代器在迭代开始时复制版本号 艺术;如果它检测到当前版本号与副本不同,则在枚举期间已修改集合,您可以引发集合修改异常


谢谢你提出的有趣的问题;我可能会在我的博客中将其作为一个例子,并可能会看看我是否能编写一个静态分析器来检测这种危险的修改。

您是否花了时间调试?只需跨出一步,看看会发生什么。这个问题因过于本地化而关闭;我不同意这种评估。我见过很多人犯这个错误。既有修改迭代集合的一般错误,也有在迭代时弹出堆栈,结果只取出一半元素的特定错误。您是否花费了调试的精力?只需跨出一步,看看会发生什么。这个问题因过于本地化而关闭;我不同意这种评估。我见过很多人犯这个错误。既有修改迭代集合的一般错误,也有在迭代集合时弹出堆栈,结果只取出一半元素的特定错误。还请注意,您可以遵循BlockingCollection的模式,并使用GetConsumingEnumerable。关键的区别在于,您并不是在对集合进行枚举的foreach主体中修改集合,而是从枚举器的定义中得到一个可枚举项,该可枚举项正在修改基础集合。这样的枚举数甚至可以从外部写入:静态IEnumerable GetConsumineGenumerable此CustomStack s{while!s.IsEmptyField返回s.Pop;}让一次性可枚举类型实现名为GetEnumerator的方法,以便允许foreach,而不实现IEnumerable,其优缺点是什么?能够获取只能枚举一次的数据当然很方便,但IEnumerable通常允许重复枚举。您不必检查数据是否增加,但也可以复制数据。我在创建枚举数时使用了它。还请注意,您可以遵循BlockingCollection的模式,并拥有一个GetConsumingEnumerable。关键的区别在于,您并不是在对集合进行枚举的foreach主体中修改集合,而是从枚举器的定义中得到一个可枚举项,该可枚举项正在修改基础集合。这样的枚举数甚至可以从外部写入:静态IEnumerable GetConsumineGenumerable此CustomStack s{while!s.IsEmptyField返回s.Pop;}让一次性可枚举类型实现名为GetEnumerator的方法,以便允许foreach,而不实现IEnumerable,其优缺点是什么?能够获取只能枚举一次的数据当然很方便,但IEnumerable通常允许重复枚举。您不必检查数据是否增加,但也可以复制数据。我在创建枚举器时使用了它。