【问题标题】:Custom Stack<T> made with IEnumerable<T> and Array?使用 IEnumerable<T> 和数组制作的自定义 Stack<T>?
【发布时间】:2013-05-05 20:30:22
【问题描述】:

我遇到了这个问题,我一直在努力解决。 我试图让 CustomStack 像 Stack 一样,只实现 Push(T)、Pop()、Peek() 和 Clear() 方法。我有这段代码,我认为它是正确的,但输出只显示了一半的数字。我认为这与push方法有关,但我看不出它有什么问题。

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];

                }
            }
        }
    }
}

【问题讨论】:

  • 你有没有花精力调试?只需一步一步看看会发生什么。
  • 这个问题因“过于本地化”而关闭;我不同意这个评价。我见过很多人犯这个错误。 (修改迭代集合的一般错误,以及在迭代时弹出堆栈并因此只取出一半元素的特定错误。)

标签: c# arrays stack ienumerable


【解决方案1】:

你正在做一些你必须永远不会做的事情:你正在修改一个集合你正在用一个枚举器迭代它。 (foreach 循环是分配枚举数的语法糖。)

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

这就是你的问题的原因;您的数据结构并非旨在 (1) 被滥用时抛出,或 (2) 被滥用时表现良好,因此当您滥用它时表现不佳。

我的建议:如果您这样做时会感到疼痛,请不要这样做。不要在枚举的循环中修改集合。

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

while(!collection.IsEmpty)  
  Console.WriteLine(collection.Pop());

这样您就不会修改集合同时处理一个枚举器

您在这里遇到的具体问题是:position 每次循环都在增加。而count 总是在减少。你说只有一半的项目被计算在内。好吧,解决它。如果你有十个项目,位置从零开始,并增加直到大于计数,然后每次循环...

position    count
 0           10
 1           9
 2           8
 3           7
 4           6
 5           5  

我们已经完成了,我们只列举了一半的项目。

如果您想让您的集合在迭代过程中被修改时保持稳健,那么position 必须在堆栈被压入或弹出时进行更改。即使计数在变化,也不能每次都盲目增加。找出正确的行为非常棘手,这就是文档建议您简单地抛出的原因。

如果你想让你的集合在被枚举时被修改时抛出异常,诀窍是让对象有一个称为“版本号”的 int。每次推送或弹出集合时,更改版本号。然后让迭代器在迭代开始时获取版本号的副本;如果它曾经检测到当前版本号与副本不同,则说明集合在枚举期间已被修改,您可以抛出集合修改异常。

感谢您提出有趣的问题;我可能会在我的博客中同时使用它作为示例,并且可能会看看我是否可以编写一个静态分析器来检测这种危险的修改。

【讨论】:

  • 另请注意,您可以遵循BlockingCollection 的模式并拥有GetConsumingEnumerable。关键区别在于您没有修改枚举它的foreach 正文中的集合,您得到一个可枚举的从枚举器的定义 正在修改底层收藏。这样的枚举器甚至可以在外部编写:static IEnumerable&lt;T&gt; GetConsumingEnumerable&lt;T&gt;(this CustomStack&lt;T&gt; s){while(!s.IsEmpty)yield return s.Pop();}
  • 让一次性可枚举类型实现一个名为GetEnumerator的方法以允许foreach而不实现IEnumerable&lt;T&gt;有什么优缺点?能够foreach 一些只能枚举一次的东西当然很方便,但通常希望IEnumerable&lt;T&gt; 允许重复枚举。
  • 您不必检查是否增加,但您也可以复制数据。我在创建枚举器时使用它。
猜你喜欢
  • 2019-01-30
  • 2018-01-05
  • 1970-01-01
  • 2011-03-01
  • 1970-01-01
  • 2012-05-28
  • 1970-01-01
  • 2012-09-17
相关资源
最近更新 更多