XP (エクストリーム プログラミング)


eXtreme Training

「Stack クラスの作成」(C# + NUnit 版)

eXtreme Training「Stack クラスの作成」(C# + NUnit 版)


ここでは,Y.Terada 氏による『エクストリーム・トレーニング』―『- eXtreme Training - Stack クラスの作成』の C# + NUnit 移植版を,Y.Terada 氏の許可を得てご紹介します.
オリジナルは,上記サイトに有ります.


○ 補足 1:
"eXtreme Training" は,Y.Terada 氏が考案したトレーニング方法です.
武芸やスポーツ等と同じように,『型』を反復練習することで,オブジェクト指向TDD (テスト駆動開発) 等を効果的に習得しようとするものです.

オブジェクト指向や TDD 等によるプログラミングを習得しよう,或るいは,収得させようととしても,初めは中々難しくうまく行かないことがあります.書籍などで学習し,理屈では判ったつもりでも実際にプログラミングで使ってみようとすると,どうしたら良いか判らない.身に付いていかない訳です.

そんなとき,先ずは,習うより慣れろでプログラミングの手本となる流れを体験してみる.そして体が覚えるまでそれを繰り返す.それによってオブジェクト指向や TDD 等を身に付けてしまおう,というのが "eXtreme Training" です.

詳しくは,Y.Terada 氏のサイト内の『エクストリーム・トレーニング』 <http://www.geocities.jp/u_1roh/columns/training.html> をご参照ください.
○ 補足 2:
NUnit は,JUnit 等と同様のユニットテスト用のツールです.詳しくは,以下のサイトを参照ください.

参考になるサイト:

○ 補足 3:
オリジナルの「eXtreme Training Stack クラスの作成」は,C++ 言語によるものですが,オリジナルの最後の方の三項目,


については,C++ 言語で問題となって C# 言語 (や Java 言語) では特に問題とならない部分ですので,割愛しました.
代わりに,オブジェクト倶楽部 ― 『車窓からの TDD』(PDF) を参考にして途中に「例外処理」の項を挿入しました.

Copyright © 2004 Y.Terada

テスト成功
テスト失敗
コンパイルエラー
凡例:

テストクラス作成


StackTest.cs (新規作成)

using System;
using NUnit.Framework;

[TestFixture]
public class StackTest
{
}

Stack.cs (新規作成)

using System;

class Stack
{
}

コンストラクト


StackTest.cs

[TestFixture]
public class StackTest
{
    [Test]
    public void TestConstructor()
    {
        Assert.Fail("no implementation");
    }
}

StackTest.cs

[TestFixture]
public class StackTest
{
    [Test]
    public void TestConstructor()
    {
        Assert.Fail("no implementation");
        Stack stack = new Stack();
        Assert.IsTrue(stack.IsEmpty);
    }
}

Stack.cs

class Stack
{
    public bool IsEmpty
    {
        get
        {
            return true; // 仮実装
        }
    }
}

Push


StackTest.cs

[TestFixture]
public class StackTest
{
    …

    [Test]
    public void TestPush()
    {
        Assert.Fail("no implementation");
    }
}

StackTest.cs

    [Test]
    public void TestPush()
    {
        Assert.Fail("no implementation");
        Stack stack = new Stack();
        stack.Push(3);
        Assert.IsFalse(stack.IsEmpty);
    }

Stack.cs

class Stack
{
    public bool IsEmpty
    {
        get
        {
            return true; // 仮実装
        }
    }

    public void Push(int x)
    {
    }
}

Stack.cs

class Stack
{
    public bool IsEmpty
    {
        get
        {
            return true; // 仮実装
            return isEmpty;
        }
    }

    public void Push(int x)
    {
        isEmpty = false;
    }

    private bool isEmpty = true;
}

StackTest.cs

    [Test]
    public void TestPush()
    {
        Stack stack = new Stack();
        stack.Push(3);
        Assert.IsFalse(stack.IsEmpty);
        Assert.AreEqual(3, stack.Top); // Top がない
    }

Stack.cs

class Stack
{
    …

    public int Top
    {
        get
        {
            return 3; // 仮実装
        }
    }

    …
}

StackTest.cs

    [Test]
    public void TestPush()
    {
        Stack stack = new Stack();
        stack.Push(3);
        Assert.IsFalse(stack.IsEmpty);
        Assert.AreEqual(3, stack.Top); // Top がない

        stack.Push(8);
        Assert.AreEqual(8, stack.Top);
    }

Stack.cs

class Stack
{
    …

    public int Top
    {
        get
        {
            return 3; // 仮実装
            return element;
        }
    }

    public void Push(int x)
    {
        isEmpty = false;
        element = x;
    }

    private bool isEmpty = true;
    private int  element;
}

StackTest.cs

    [Test]
    public void TestPush()
    {
        Stack stack = new Stack();
        stack.Push(3);
        Assert.IsFalse(stack.IsEmpty);
        Assert.AreEqual(3, stack.Top);

        stack.Push(8);
        Assert.AreEqual(8, stack.Top);

        stack.Top = 10;
        Assert.AreEqual(10, stack.Top);
    }

Stack.cs

class Stack
{
    …

    public int Top
    {
        get
        {
             return element;
        }
        set
        {
            element = value;
        }
    }

    …
}

Size プロパティ


StackTest.cs

    [Test]
    public void TestConstructor()
    {
        Stack stack = new Stack();
        Assert.IsTrue(stack.IsEmpty);
        Assert.AreEqual(0, stack.Size);
    }

Stack.cs

class Stack
{
    …

    public int Size
    {
        get
        {
            return 0; // 仮実装
        }
    }

    …
}

StackTest.cs

    [Test]
    public void TestPush()
    {
        Stack stack = new Stack();
        stack.Push(3);
        Assert.IsFalse(stack.IsEmpty);
        Assert.AreEqual(1, stack.Size);
        Assert.AreEqual(3, stack.Top );

        stack.Push(8);
        Assert.AreEqual(8, stack.Top);

        …
    }

Stack.cs

class Stack
{
    …

    public int Size
    {
        get
        {
            return 0; // 仮実装
            return isEmpty ? 0 : 1; // 仮実装
        }
    }

    …
}

StackTest.cs

    [Test]
    public void TestPush()
    {
        Stack stack = new Stack();
        stack.Push(3);
        Assert.IsFalse(stack.IsEmpty);
        Assert.AreEqual(1, stack.Size);
        Assert.AreEqual(3, stack.Top );

        stack.Push(8);
        Assert.AreEqual(2, stack.Size);
        Assert.AreEqual(8, stack.Top );

        …
    }

Stack.cs

class Stack
{
    …

    public int Size
    {
        get
        {
            return isEmpty ? 0 : 1; // 仮実装
            return size;
        }
    }

    …

    public void Push(int x)
    {
        isEmpty = false;
        element = x;
        ++size;
    }

    private bool isEmpty = true;
    private int  size = 0;
    private int  element;
}

Stack.cs

…リファクタリング…

class Stack
{
    …

    public bool IsEmpty
    {
        get
        {
            return isEmpty;
            return (size == 0);
        }
    }

    …

    public void Push(int x)
    {
        isEmpty = false;
        element = x;
        ++size;
    }

    private bool isEmpty = true;
    private int  size = 0;
    private int  element;
}

Pop


StackTest.cs

[TestFixture]
public class StackTest
{
    …

    [Test]
    public void TestPop()
    {
        Assert.Fail("no implementation");
    }
}

StackTest.cs

    [Test]
    public void TestPop()
    {
        Assert.Fail("no implementation");
        Stack stack = new Stack();
        stack.Push(3);
        stack.Pop();
        Assert.IsTrue(stack.IsEmpty);
        Assert.AreEqual(0, stack.Size);
    }

Stack.cs

class Stack
{
    …

    public void Pop()
    {
        size = 0; // 仮実装
    }

    …
}

StackTest.cs

    [Test]
    public void TestPop()
    {
        Stack stack = new Stack();
        stack.Push(3);
        stack.Push(5);

        stack.Pop();
        Assert.AreEqual(1, stack.Size);

        stack.Pop();
        Assert.IsTrue(stack.IsEmpty);
        Assert.AreEqual(0, stack.Size);
    }

Stack.cs

class Stack
{
    …

    public void Pop()
    {
        size = 0; // 仮実装
        System.Diagnostics.Debug.Assert(size > 0);
        --size;
    }

    …
}

StackTest.cs

    [Test]
    public void TestPop()
    {
        Stack stack = new Stack();
        stack.Push(3);
        stack.Push(5);

        stack.Pop();
        Assert.AreEqual(1, stack.Size);
        Assert.AreEqual(3, stack.Top );

        stack.Pop();
        Assert.IsTrue(stack.IsEmpty);
        Assert.AreEqual(0, stack.Size);
    }

Stack.cs

class Stack
{
    …

    public int Top
    {
        get
        {
            return element;
            System.Diagnostics.Debug.Assert(size > 0);
            return elements[size - 1];
        }
        set
        {
            element = value;
            System.Diagnostics.Debug.Assert(size > 0);
            elements[size - 1] = value;
        }
    }

    public void Push(int x)
    {
        element = x;
        ++size;
        System.Diagnostics.Debug.Assert(size < 2);
        elements[size++] = x;
    }

    …

    private int   size = 0;
    private int   element;
    private int[] elements = new int[2];	
}

スタックサイズの一般化


Stack.cs

class Stack
{
    …

    public void Push(int x)
    {
        System.Diagnostics.Debug.Assert(size < 2);
        int[] temporary = new int[size + 1];
        if (elements != null)
            Array.Copy(elements, temporary, size);
        elements = temporary;
        elements[size++] = x;
    }

    …

    private int   size = 0;
    private int[] elements = new int[2];
    private int[] elements = null;
}

Stack.cs

class Stack
{
    …

    public void Push(int x)
    {
        int[] temporary = new int[size + 1];
        if (elements != null)
            Array.Copy(elements, temporary, size);
        elements = temporary;
        if (capacity <= size)
        {
            capacity = (capacity == 0) ? 8 : capacity * 2;
            int[] temporary = new int[capacity];
            if (elements != null)
                Array.Copy(elements, temporary, size);
            elements = temporary;
        }
        elements[size++] = x;
    }

    …

    private int   size = 0;
    private int   capacity = 0;
    private int[] elements = null;
}

コピー操作


StackTest.cs

[Test]
    public void TestClone()
    {
        Assert.Fail("no implementation");
    }

StackTest.cs

    [Test]
    public void TestClone()
    {
        Assert.Fail("no implementation");
        Stack source	  = new Stack();
        source.Push(3);

        Stack destination = (Stack)source.Clone();
    }

Stack.cs

class Stack : ICloneable
{
    …

    public object Clone()
    {
        Stack stack = new Stack();
        return stack;
    }

    …
}

StackTest.cs

    [Test]
    public void TestClone()
    {
        Stack source = new Stack();
        source.Push(3);

        Stack destination = (Stack)source.Clone();
        Assert.AreEqual(source.Top , destination.Top );
        Assert.AreEqual(source.Size, destination.Size);

        destination.Pop();
        destination.Push(5);
        Assert.IsFalse(source.Top == destination.Top);
    }

Stack.cs

class Stack : ICloneable
{
    …

    public object Clone()
    {
        Stack stack = new Stack();
        stack.size     = size    ;
        stack.capacity = capacity;
        if (elements != null)
        {
            stack.elements = (int[])elements.Clone();
        }
        return stack;
    }

    …
}

StackTest.cs

[Test]
    public void TestCopy()
    {
        Assert.Fail("no implementation");
    }

StackTest.cs

    [Test]
    public void TestCopy()
    {
        Assert.Fail("no implementation");
        Stack source      = new Stack();
        source.Push(3);

        Stack destination = new Stack();
        Stack.Copy(source, destination);
    }

Stack.cs

class Stack : ICloneable
{
    …

    public static void Copy(Stack source, Stack destination)
    {
    }

    …
}

StackTest.cs

    [Test]
    public void TestCopy()
    {
        Stack source      = new Stack();
        source.Push(3);

        Stack destination = new Stack();
        Stack.Copy(source, destination);
        Assert.AreEqual(source.Top , destination.Top );
        Assert.AreEqual(source.Size, destination.Size);

        destination.Pop();
        destination.Push(5);
        Assert.IsFalse(source.Top == destination.Top);
    }

Stack.cs

class Stack : ICloneable
{
    …

    public static void Copy(Stack source, Stack destination)
    {
        destination.size     = source.size    ;
        destination.capacity = source.capacity;
        destination.elements = new int[source.capacity];
        if (source.size > 0)
        {
            Array.Copy(source.elements, destination.elements, source.size);
        }
    }

    …
}

例外処理


StackTest.cs

    [Test]
    public void TestEmptyTop()
    {
        Assert.Fail("no implementation");
    }

StackTest.cs

    [Test]
    public void TestEmptyTop()
    {
        Assert.Fail("no implementation");
        Stack stack = new Stack();
        try
        {
            int x = stack.Top;
            Assert.Fail("ここには来ない筈");
        }
        catch (InvalidOperationException expected)
        {
        }
    }

Stack.cs

    public int Top
    {
        get
        {
            System.Diagnostics.Debug.Assert(size > 0);
            if (IsEmpty)
                 throw new InvalidOperationException();
            return elements[size - 1];
        }
        set
        {
            System.Diagnostics.Debug.Assert(size > 0);
            elements[size - 1] = value;
        }
    }

StackTest.cs

    [Test]
    public void TestEmptyTop()
    {
        Stack stack = new Stack();
        try
        {
            int x = stack.Top;
            Assert.Fail("ここには来ない筈");
        }
        catch (InvalidOperationException expected)
        {
        }
        try
        {
            stack.Top = 1;
            Assert.Fail("ここには来ない筈");
        }
        catch (InvalidOperationException expected)
        {
        }
    }

Stack.cs

    public int Top
    {
        get
        {
            if (IsEmpty)
                 throw new InvalidOperationException();
            return elements[size - 1];
        }
        set
        {
            System.Diagnostics.Debug.Assert(size > 0);
            if (IsEmpty)
                 throw new InvalidOperationException();
            elements[size - 1] = value;
        }
    }

Stack.cs

…リファクタリング…

class Stack : ICloneable
{
    …

    public int Top
    {
        get
        {
            if (IsEmpty)
                 throw new InvalidOperationException();
            EmptyCheck();
            return elements[size - 1];
        }
        set
        {
            if (IsEmpty)
                 throw new InvalidOperationException();
            EmptyCheck();
            elements[size - 1] = value;
        }
    }

    …

    private void EmptyCheck()
    {
        if (IsEmpty)
            throw new InvalidOperationException();
    }

    …
}

StackTest.cs

    [Test]
    public void TestEmptyPop()
    {
        Assert.Fail("no implementation");
    }

StackTest.cs

    [Test]
    public void TestEmptyPop()
    {
        Assert.Fail("no implementation");
        Stack stack = new Stack();
        try
        {
            stack.Pop();
            Assert.Fail("ここには来ない筈");
        }
        catch (InvalidOperationException expected)
        {
        }
    }

Stack.cs

    public void Pop()
    {
        System.Diagnostics.Debug.Assert(size > 0);
        EmptyCheck();
        --size;
    }

Swap() の導入 (リファクタリング)


StackTest.cs

    [Test]
    public void TestSwap()
    {
        Assert.Fail("no implementation");
    }

StackTest.cs

    [Test]
    public void TestSwap()
    {
        Assert.Fail("no implementation");
        Stack stack1 = new Stack();
        stack1.Push(1);
        stack1.Push(3);
        stack1.Push(5);
        Assert.AreEqual(3, stack1.Size);
        Assert.AreEqual(5, stack1.Top );

        Stack stack2 = new Stack();
        stack2.Push(2);
        stack2.Push(4);
        Assert.AreEqual(2, stack2.Size);
        Assert.AreEqual(4, stack2.Top );

        stack1.Swap(stack2);
        Assert.AreEqual(2, stack1.Size);
        Assert.AreEqual(4, stack1.Top );
        Assert.AreEqual(3, stack2.Size);
        Assert.AreEqual(5, stack2.Top );
    }

Stack.cs

class Stack : ICloneable
{
    …

    public void Swap(Stack stack)
    {
        Swap(ref size    , ref stack.size    );
        Swap(ref capacity, ref stack.capacity);
        int[] temporary = elements      ;
        elements        = stack.elements;
        stack.elements  = temporary     ;
    }private static void Swap(ref int x, ref int y)
    {
        int temporary = x        ;
        x             = y        ;
        y             = temporary;
    }

    …
}

Stack.cs

class Stack : ICloneable
{
    …

    public object Clone()
    {
        Stack stack = new Stack();
        stack.size     = size;
        stack.capacity = capacity;
        stack.capacity = IsEmpty ? 8 : size * 2;
        if (elements != null)
        {
            stack.elements = (int[])elements.Clone();
        }
        stack.elements = new int[stack.capacity];
        if (size > 0)
        {
            Array.Copy(elements, stack.elements, size);
        }
        return stack;
    }

    public static void Copy(Stack source, Stack destination)
    {
        destination.size     = source.size;
        destination.capacity = source.capacity;
        destination.capacity = source.IsEmpty ? 8 : source.size * 2;
        destination.elements = new int[source.capacity];
        destination.elements = new int[destination.capacity];
        if (source.size > 0)
        {
            Array.Copy(source.elements, destination.elements, source.size);
        }
    }

    …

    public void Push(int x)
    {
        if (capacity <= size)
        {
            capacity = (capacity == 0) ? 8 : capacity * 2;
            int[] temporary = new int[capacity];
            if (elements != null)
                Array.Copy(elements, temporary, size);
            elements = temporary;
            Stack temporary = (Stack)Clone();
            Swap(temporary);
       }
        elements[size++] = x;
    }

    …
}

Stack.cs

class Stack : ICloneable
{
    …

    public void Push(int x)
    {
        if (capacity <= size)
        {
            Stack temporary = (Stack)Clone();
            Swap(temporary);
            Swap((Stack)Clone());
        }
        elements[size++] = x;
    }

    …
}

Generics 化

○ 補足 4:
C++ の template や Java の Generics に似た機能である C# の Generics は,近い将来のバージョンで使えるようになります.
詳しくは,以下のサイトを参照ください.

参照サイト:


尚,以下では,Generics が使用可能な場合と使用できない場合に分けてあります.

StackTest.cs

● Generics が使用可能なとき

[TestFixture]
public class StackTest
{
    [Test]
    public void TestConstructor()
    {
        Stack stack = new Stack();
        Stack<int> stack = new Stack<int>();

        …
    }

    [Test]
    public void TestClone()
    {
        Stack source = new Stack();
        Stack<int> source = new Stack<int>();
        source.Push(3);

        Stack destination = (Stack)source.Clone();
        Stack<int> destination = (Stack<int>)source.Clone();

        …
    }

    [Test]
    public void TestCopy()
    {

        Stack source = new Stack();
        Stack<int> source = new Stack<int>();
        source.Push(3);

        Stack destination = new Stack();
        Stack<int> destination = new Stack<int>();

        …
    }

    [Test]
    void TestSwap()
    {
        Stack stack1 = new Stack();
        Stack<int> stack1 = new Stack<int>();Stack stack2 = new Stack();
        Stack<int> stack2 = new Stack<int>();

        …
    }

    [Test]
    public void TestPush()
    {
        Stack stack = new Stack();
        Stack<int> stack = new Stack<int>();

        …
    }

    [Test]
    public void TestPop()
    {
        Stack stack = new Stack();
        Stack<int> stack = new Stack<int>();

        …
    }

    [Test]
    public void TestEmptyTop()
    {
        Stack stack = new Stack();
        Stack<int> stack = new Stack<int>();

        …
    }


    [Test]
    public void TestEmptyPop()
    {
        Stack stack = new Stack();
        Stack<int> stack = new Stack<int>();

        …
    }
}

● Generics が使用できないとき

    [Test]
    public void TestEmptyTop()
    {
        Stack stack = new Stack();
        try
        {
            int x = stack.Top;
            int x = (int)stack.Top;
            Assert.Fail("ここには来ない筈");
        }

        …
    }

Stack.cs

● Generics が使用可能なときのみ

class Stack : ICloneable
class Stack<T> : ICloneable
{
    …
}

StackTest.cs

    [Test]
    public void TestForMyValue()
    {
        Assert.Fail("no implementation");
    }

StackTest.cs

● Generics が使用可能なとき

    class MyValue
    {
        public int number;
    }

    [Test]
    public void TestForMyValue()
    {
        Assert.Fail("no implementation");
        Stack<MyValue> stack = new Stack<MyValue>();
        MyValue v = new MyValue();
        v.number = 1;
        stack.Push(v);
        Assert.AreEqual(1, stack.Size);
        Assert.AreEqual(1, stack.Top.number);
        stack.Pop();
        Assert.IsTrue(stack.IsEmpty);
    }

● Generics が使用できないとき

    class MyValue
    {
        public int number;
    }

    [Test]
    public void TestForMyValue()
    {
        Assert.Fail("no implementation");
        Stack stack = new Stack();
        MyValue v = new MyValue();
        v.number = 1;
        stack.Push(v);
        Assert.AreEqual(1, stack.Size);
        Assert.AreEqual(1, ((MyValue)stack.Top).number);
        stack.Pop();
        Assert.IsTrue(stack.IsEmpty);
    }

Stack.cs

● Generics が使用可能なとき

class Stack<T> : ICloneable
{
    …

    public int Top
    public T Top
    {
        get
        {
            EmptyCheck();
            return elements[size - 1];
        }
        set
        {
            EmptyCheck();
            elements[size - 1] = value;
        }
    }

    public object Clone() // shallow copy (簡易コピー)
    {
        Stack stack = new Stack();
        stack.size     = size;
        stack.capacity = IsEmpty ? 8 : size * 2;
        stack.elements = new int[stack.capacity];
        stack.elements = new T[stack.capacity];
        if (size > 0)
        {
            Array.Copy(elements, stack.elements, size);
        }
        return stack;
    }

    public static void Copy(Stack source, Stack destination) // shallow copy (簡易コピー)
    {
        destination.size     = source.size;
        destination.capacity = source.IsEmpty ? 8 : source.size * 2;
        destination.elements = new int[destination.capacity];
        destination.elements = new T[destination.capacity];
        if (source.size > 0)
        {
            Array.Copy(source.elements, destination.elements, source.size);
        }
    }

    public void Swap(Stack stack)
    {
        Swap(ref size    , ref stack.size    );
        Swap(ref capacity, ref stack.capacity);
        int[] temporary    = elements      ;
        T[] temporary      = elements      ;
        elements           = stack.elements;
        stack.elements     = temporary     ;
    }

    public void Push(int x)
    public void Push(T x)
    {
        if (capacity <= size)
        {
            Swap((Stack)Clone());
        }
        elements[size++] = x;
    }

    …

    private int size = 0;
    private int capacity = 0;
    private int[] elements = null;
    private T[] elements = null;
}

● Generics が使用できないとき

class Stack : ICloneable
{
    …

    public int Top
    public object Top
    {
        get
        {
            EmptyCheck();
            return elements[size - 1];
        }
        set
        {
            EmptyCheck();
            elements[size - 1] = value;
        }
    }

    public object Clone() // shallow copy (簡易コピー)
    {
        Stack stack = new Stack();
        stack.size     = size;
        stack.capacity = IsEmpty ? 8 : size * 2;
        stack.elements = new int[stack.capacity];
        stack.elements = new object[stack.capacity];
        if (size > 0)
        {
            Array.Copy(elements, stack.elements, size);
        }
        return stack;
    }

    public static void Copy(Stack source, Stack destination) // shallow copy (簡易コピー)
    {
        destination.size     = source.size;
        destination.capacity = source.IsEmpty ? 8 : source.size * 2;
        destination.elements = new int[destination.capacity];
        destination.elements = new object[destination.capacity];
        if (source.size > 0)
        {
            Array.Copy(source.elements, destination.elements, source.size);
        }
    }

    public void Swap(Stack stack)
    {
        Swap(ref size    , ref stack.size    );
        Swap(ref capacity, ref stack.capacity);
        int[] temporary    = elements      ;
        object[] temporary = elements      ;
        elements           = stack.elements;
        stack.elements     = temporary     ;
    }

    public void Push(int x)
    public void Push(object x)
    {
        if (capacity <= size)
        {
            Swap((Stack)Clone());
        }
        elements[size++] = x;
    }

    …

    private int      size = 0;
    private int      capacity = 0;
    private int[] elements = null;
    private object[] elements = null;
}

最終コード


● Generics が使用可能なとき

StackTest.cs

using System;
using NUnit.Framework;

[TestFixture]
public class StackTest
{
    [Test]
    public void TestConstructor()
    {
        Stack<int> stack = new Stack<int>();
        Assert.IsTrue(stack.IsEmpty);
        Assert.AreEqual(0, stack.Size);
    }

    [Test]
    public void TestCopy()
    {
        Stack<int> source = new Stack<int>();
        source.Push(3);

        Stack<int> destination = (Stack<int>)source.Clone();
        Assert.AreEqual(source.Top , destination.Top );
        Assert.AreEqual(source.Size, destination.Size);

        destination.Pop();
        destination.Push(5);
        Assert.IsFalse(source.Top == destination.Top);
    }

    [Test]
    public void TestSwap()
    {
        Stack<int> stack1 = new Stack<int>();
        stack1.Push(1);
        stack1.Push(3);
        stack1.Push(5);
        Assert.AreEqual(3, stack1.Size);
        Assert.AreEqual(5, stack1.Top );

        Stack<int> stack2 = new Stack<int>();
        stack2.Push(2);
        stack2.Push(4);
        Assert.AreEqual(2, stack2.Size);
        Assert.AreEqual(4, stack2.Top );

        stack1.Swap(stack2);
        Assert.AreEqual(2, stack1.Size);
        Assert.AreEqual(4, stack1.Top );
        Assert.AreEqual(3, stack2.Size);
        Assert.AreEqual(5, stack2.Top );
    }

    [Test]
    public void TestPush()
    {
        Stack stack<int> = new Stack<int>();
        stack.Push(3);
        Assert.IsFalse(stack.IsEmpty);
        Assert.AreEqual(1, stack.Size);
        Assert.AreEqual(3, stack.Top );

        stack.Push(8);
        Assert.AreEqual(2, stack.Size);
        Assert.AreEqual(8, stack.Top );

        stack.Top = 10;
        Assert.AreEqual(10, stack.Top);
    }

    [Test]
    public void TestPop()
    {
        Stack stack<int> = new Stack<int>();
        stack.Push(3);
        stack.Push(5);

        stack.Pop();
        Assert.AreEqual(1, stack.Size);
        Assert.AreEqual(3, stack.Top );

        stack.Pop();
        Assert.IsTrue(stack.IsEmpty);
        Assert.AreEqual(0, stack.Size);
    }

    [Test]
    public void TestEmptyTop()
    {
        Stack<int> stack = new Stack<int>();
        try
        {
            int x = stack.Top;
            Assert.Fail("ここには来ない筈");
        }
        catch (InvalidOperationException expected)
        {
        }
        try
        {
            stack.Top = 1;
            Assert.Fail("ここには来ない筈");
        }
        catch (InvalidOperationException expected)
        {
        }
    }

    [Test]
    public void TestEmptyPop()
    {
        Stack<int> stack = new Stack<int>();
        try
        {
            stack.Pop();
            Assert.Fail("ここには来ない筈");
        }
        catch (InvalidOperationException expected)
        {
        }
    }

    class MyValue
    {
        public int number;
    }

    [Test]
    public void TestForMyValue()
    {
        Stack<MyValue> stack = new Stack<MyValue>();
        MyValue v = new MyValue();
        v.number = 1;
        stack.Push(v);
        Assert.AreEqual(1, stack.Size);
        Assert.AreEqual(1, stack.Top.number);
        stack.Pop();
        Assert.IsTrue(stack.IsEmpty);
    }
}

Stack.cs

using System;

class Stack<T> : ICloneable
{

    public bool IsEmpty
    {
        get
        {
            return (size == 0);
        }
    }

    public int Size
    {
        get
        {
            return size;
        }
    }

    public T Top
    {
        get
        {
            EmptyCheck();
            return elements[size - 1];
        }
        set
        {
            EmptyCheck();
            elements[size - 1] = value;
        }
    }

    public object Clone() // shallow copy (簡易コピー)
    {
        Stack stack = new Stack();
        stack.size     = size;
        stack.capacity = IsEmpty ? 8 : size * 2;
        stack.elements = new T[stack.capacity];
        if (size > 0)
        {
            Array.Copy(elements, stack.elements, size);
        }
        return stack;
    }

    public static void Copy(Stack source, Stack destination) // shallow copy (簡易コピー)
    {
        destination.size     = source.size;
        destination.capacity = source.IsEmpty ? 8 : source.size * 2;
        destination.elements = new T[destination.capacity];
        if (source.size > 0)
        {
            Array.Copy(source.elements, destination.elements, source.size);
        }
    }

    public void Swap(Stack stack)
    {
        Swap(ref size    , ref stack.size    );
        Swap(ref capacity, ref stack.capacity);
        T[] temporary  = elements      ;
        elements       = stack.elements;
        stack.elements = temporary     ;
    }

    public void Push(T x)
    {
        if (capacity <= size)
        {
            Swap((Stack)Clone());
        }
        elements[size++] = x;
    }

    public void Pop()
    {
        EmptyCheck();
        --size;
    }

    private void EmptyCheck()
    {
        if (IsEmpty)
            throw new InvalidOperationException();
    }

    private static void Swap(ref int x, ref int y)
    {
        int temporary = x        ;
        x             = y        ;
        y             = temporary;
    }

    private int size = 0;
    private int capacity = 0;
    private T[] elements = null;
}

● Generics が使用可でないとき

StackTest.cs

using System;
using NUnit.Framework;

[TestFixture]
public class StackTest
{

    [Test]
    public void TestConstructor()
    {
        Stack stack = new Stack();
        Assert.IsTrue(stack.IsEmpty);
        Assert.AreEqual(0, stack.Size);
    }

    [Test]
    public void TestClone()
    {
        Stack source = new Stack();
        source.Push(3);

        Stack destination = (Stack)source.Clone();
        Assert.AreEqual(source.Top , destination.Top );
        Assert.AreEqual(source.Size, destination.Size);

        destination.Pop();
        destination.Push(5);
        Assert.IsFalse(source.Top == destination.Top);
    }

    [Test]
    public void TestCopy()
    {
        Stack source      = new Stack();
        source.Push(3);

        Stack destination = new Stack();
        Stack.Copy(source, destination);

        Assert.AreEqual(source.Top , destination.Top );
        Assert.AreEqual(source.Size, destination.Size);

        destination.Pop();
        destination.Push(5);
        Assert.IsFalse(source.Top == destination.Top);
    }

    [Test]
    public void TestSwap()
    {
        Stack stack1 = new Stack();
        stack1.Push(1);
        stack1.Push(3);
        stack1.Push(5);
        Assert.AreEqual(3, stack1.Size);
        Assert.AreEqual(5, stack1.Top );

        Stack stack2 = new Stack();
        stack2.Push(2);
        stack2.Push(4);
        Assert.AreEqual(2, stack2.Size);
        Assert.AreEqual(4, stack2.Top );

        stack1.Swap(stack2);
        Assert.AreEqual(2, stack1.Size);
        Assert.AreEqual(4, stack1.Top );
        Assert.AreEqual(3, stack2.Size);
        Assert.AreEqual(5, stack2.Top );
    }

    [Test]
    public void TestPush()
    {
        Stack stack = new Stack();
        stack.Push(3);
        Assert.IsFalse(stack.IsEmpty);
        Assert.AreEqual(1, stack.Size);
        Assert.AreEqual(3, stack.Top );

        stack.Push(8);
        Assert.AreEqual(2, stack.Size);
        Assert.AreEqual(8, stack.Top );

        stack.Top = 10;
        Assert.AreEqual(10, stack.Top);
    }

    [Test]
    public void TestPop()
    {
        Stack stack = new Stack();
        stack.Push(3);
        stack.Push(5);

        stack.Pop();
        Assert.AreEqual(1, stack.Size);
        Assert.AreEqual(3, stack.Top );

        stack.Pop();
        Assert.IsTrue(stack.IsEmpty);
        Assert.AreEqual(0, stack.Size);
    }

    [Test]
    public void TestEmptyTop()
    {
        Stack stack = new Stack();
        try
        {
            int x = (int)stack.Top;
            Assert.Fail("ここには来ない筈");
        }
        catch (InvalidOperationException expected)
        {
        }
        try
        {
            stack.Top = 1;
            Assert.Fail("ここには来ない筈");
        }
        catch (InvalidOperationException expected)
        {
        }
    }

    [Test]
    public void TestEmptyPop()
    {
        Stack stack = new Stack();
        try
        {
            stack.Pop();
            Assert.Fail("ここには来ない筈");
        }
        catch (InvalidOperationException expected)
        {
        }
    }

    class MyValue
    {
        public int number;
    }

    [Test]
    public void TestForMyValue()
    {
        Stack stack = new Stack();
        MyValue v = new MyValue();
        v.number = 1;
        stack.Push(v);
        Assert.AreEqual(1, stack.Size);
        Assert.AreEqual(1, ((MyValue)stack.Top).number);
        stack.Pop();
        Assert.IsTrue(stack.IsEmpty);
    }
}

Stack.cs

using System;

class Stack : ICloneable
{
    public bool IsEmpty
    {
        get
        {
            return (size == 0);
        }
    }

    public int Size
    {
        get
        {
            return size;
        }
    }

    public object Top
    {
        get
        {
            EmptyCheck();
            return elements[size - 1];
        }
        set
        {
            EmptyCheck();
            elements[size - 1] = value;
        }
    }

    public object Clone() // shallow copy (簡易コピー)
    {
        Stack stack = new Stack();
        stack.size     = size;
        stack.capacity = IsEmpty ? 8 : size * 2;
        stack.elements = new object[stack.capacity];
        if (size > 0)
        {
            Array.Copy(elements, stack.elements, size);
        }
        return stack;
    }

    public static void Copy(Stack source, Stack destination) // shallow copy (簡易コピー)
    {
        destination.size     = source.size;
        destination.capacity = source.IsEmpty ? 8 : source.size * 2;
        destination.elements = new object[destination.capacity];
        if (source.size > 0)
        {
            Array.Copy(source.elements, destination.elements, source.size);
        }
    }

    public void Swap(Stack stack)
    {
        Swap(ref size    , ref stack.size    );
        Swap(ref capacity, ref stack.capacity);
        object[] temporary = elements      ;
        elements           = stack.elements;
        stack.elements     = temporary     ;
    }

    public void Push(object x)
    {
        if (capacity <= size)
        {
            Swap((Stack)Clone());
        }
        elements[size++] = x;
    }

    public void Pop()
    {
        EmptyCheck();
        --size;
    }

    private void EmptyCheck()
    {
        if (IsEmpty)
            throw new InvalidOperationException();
    }

    private static void Swap(ref int x, ref int y)
    {
        int temporary = x        ;
        x             = y        ;
        y             = temporary;
    }

    private int      size = 0;
    private int      capacity = 0;
    private object[] elements = null;
}

関連サイト


参考文献