抽象クラス

抽象クラスとは

オーバーライドと多様性では仮想メソッドのオーバーライドについて説明しました。
仮想メソッドをオーバーライドするか否かは自由ですが、時にはオーバーライドを強制したい場合もあります。

例えば色々な国の人のクラスを定義して、それぞれの国の言語を話すプログラムを作ることを想定してみます。


class Human
{
    public string Name;

    public Human(string name)
    {
        Name = name;
    }

    public virtual void Speak()
    {
        //派生クラスで定義するので空
    }
}

class American : Human
{
    public American(string name)
        : base(name) { }

    public override void Speak()
    {
        Console.WriteLine("I'm {0}.", Name);
    }
}

class Japanese : Human
{
    public Japanese(string name)
        : base(name) { }

    public override void Speak()
    {
        Console.WriteLine("私は{0}です。", Name);
    }
}

言語の違いはフィールドで持つのが一般的かと思いますが、今回はメソッドに直接定義してみます。

共通する部分は基底クラスで定義して、動作が異なる部分を派生クラスで定義します。
基底クラスからこのメソッドを使用することはありませんが、省略するとオーバーライドができないので空で定義しています。

しかし使わないメソッドを定義するのは無駄です。
そもそもHumanクラスは派生クラスを作るために必要ですが、この基底クラスはインスタンスが生成できなくても構いません。
むしろ意味のない動作をするようなインスタンスは生成できないほうが紛らわしくなく親切とも言えます。

このような場合は抽象クラス(ちゅうしょうクラス)と抽象メソッドという機能を使うとスッキリします。

抽象クラスと抽象メソッドの書式


//抽象クラス
abstract class Human
{
    public string Name;

    public Human(string name)
    {
        Name = name;
    }

    //抽象メソッド
    public abstract void Speak();
}

//Americanクラスと
//Japaneseクラスの定義は
//同じなので省略

抽象クラスはclassの前にabstractキーワードを付けて定義します。
(abstract=抽象的な)

抽象クラスはインスタンスが生成できないクラスとなります。
つまり継承して派生クラスでインスタンスを生成して使うことを前提としたクラスになります。

抽象クラス内ではメソッドとプロパティにもabstractキーワードを付けることができます。
(抽象クラス以外のクラスでは不可)
これは抽象メソッドといい、派生クラスで必ずオーバーライドしなければならないメソッド(プロパティ)となります。
派生クラスでオーバーロードしないとエラーになります。
必ずオーバーライドされるので、基底クラスではメソッドの定義自体は持ちません。
(ブロック{}さえも書かない)

抽象クラスと抽象メソッドを使用したコードの全体は以下になります。


//抽象クラス
abstract class Human
{
    public string Name;

    public Human(string name)
    {
        Name = name;
    }

    //抽象メソッド
    public abstract void Speak();
}

class American : Human
{
    public American(string name)
        : base(name) { }

    public override void Speak()
    {
        Console.WriteLine("I'm {0}.", Name);
    }
}

class Japanese : Human
{
    public Japanese(string name)
        : base(name) { }

    public override void Speak()
    {
        Console.WriteLine("私は{0}です。", Name);
    }
}

static void Main(string[] args)
{
    //Humanクラスのインスタンス生成不可
    //Human human = new Human();

    List<Human> lst = new List<Human>();
    lst.Add(new American("John"));
    lst.Add(new Japanese("太郎"));

    foreach (var e in lst)
        e.Speak();

    Console.ReadLine();
}
I'm John.
私は太郎です。

抽象プロパティ

プロパティもクラス内から見ればメソッドのようなものなので、プロパティもabstractにできます。
プロパティの場合、抽象クラス内ではアクセサの指定のみが可能で、実装は派生クラスで行います。


abstract class Base
{
    //抽象プロパティ
    public abstract int Num { get; set; }
}

class Derived : Base
{
    public override int Num
    {
        get { return Num; }
        set
        {
            Num = Math.Max(0, value);
        }
    }
}

抽象プロパティのアクセサと派生クラスのアクセサが異なるとエラーになります。


abstract class Base
{
    public abstract int Num1 { get; }
    public abstract int Num2 { get; set; }
}

class Derived : Base
{
    //アクセサの指定が違うので両方エラー
    public override int Num1 { get; set; }
    public override int Num2 { get; }
}