1

私は理解しようとしてきたこの問題を抱えています。Push(T)、Pop()、Peek()、および Clear() メソッドのみを実装して、CustomStack を Stack のように動作させようとしています。このコードは正しいと思いましたが、出力には数字の半分しか表示されません。プッシュ方式の関係かと思いますが、どこが悪いのかわかりません。

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

                }
            }
        }
    }
}
4

1 に答える 1

15

決してしてはいけないことをしている:列挙子で反復している間にコレクションを変更している。(ループは、列挙子を割り当てるための構文糖衣です。) foreach

実際のドキュメントは、列挙中にデータ構造が変更された場合、あなたのような実装が例外をスローするIEnumerableことを示唆しています。( で試してみるとわかります。 でリストが列挙されているときにアイテムを追加または削除すると、リストがスローされます。)List<T>foreach

それが問題の原因です。あなたのデータ構造は、(1)悪用されたときにスローするか、(2)悪用されたときにうまく動作するように設計されていないため、悪用されたときにうまく動作しません。

私のアドバイス:それをするときに痛い場合は、それをしないでください. コレクションを列挙しているループ中にコレクションを変更しないでください。

代わりに、IsEmptyプロパティを作成してループを記述します。

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

そうすれば、列挙子が同時に処理されている間にコレクションを変更することはありません。

ここにある特定の問題は次のとおりpositionです。ループを通過するたびに常に増加しています。そしてcount常に減少しています。あなたは、アイテムの半分だけがカウントされていると言います。さて、それを解決します。10個のアイテムがある場合、位置はゼロから始まり、カウントより大きくなるまで増加し、ループを通過するたびに...

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

これで、アイテムの半分しか列挙できませんでした。

反復中に変更されてもコレクションを堅牢にしたい場合positionは、スタックがプッシュまたはポップされたときに変更する必要があります。カウントが変化しているにもかかわらず、毎回やみくもに増加することはできません。正しい動作を理解するのは非常に難しいため、ドキュメントでは単純にスローすることを推奨しています。

列挙中に変更されたときにコレクションに例外をスローさせたい場合は、オブジェクトに「バージョン番号」と呼ばれる int を持たせるのがコツです。コレクションをプッシュまたはポップするたびに、バージョン番号を変更してください。次に、反復の開始時に反復子にバージョン番号のコピーを取得させます。現在のバージョン番号がコピーと異なることが検出された場合、コレクションは列挙中に変更されており、コレクション変更例外をスローできます。

興味深い質問をありがとう。これをブログの例として使用し、この種の危険な変更を検出する静的アナライザーを作成できるかどうかを確認します。

于 2013-05-11T06:23:42.577 に答える