インターフェイス

インターフェイスとは

抽象クラスではインスタンスを生成できないクラスの定義の仕方を説明しました。
この機能をさらに推し進めたのがインターフェイス(インターフェース)です。
(interface=接点)
抽象クラスと同じく、インターフェイスも継承されることを前提とした機能です。
インターフェイスはおおむね

  • インスタンスは生成できない
  • メンバーはすべてpublic(自動)
  • メンバーはすべてabstract(自動)
    ただし派生クラスでoverrideキーワードは使用しない
  • フィールドは持てない
  • コンストラクター、ファイナライザーがない
  • staticメンバーは持てない
  • いくつでも継承元に指定できる

というものです。
特に赤字の特徴は通常のクラスや抽象クラスでは実現できない、インターフェイス独自の重要な機能です。

インターフェイスの書式

インターフェイスはinterfaceキーワードで定義します。
これはクラスではなくインターフェイスという独立したものですのでclassキーワードは付けません。
名前は自由ですが、インターフェイスであることを明確にするために慣習的に先頭に「I」が付けられます。


//インターフェイス
//慣習的に名前の先頭には「I」を付ける
interface ITest
{
    //フィールドは持てない
    //int num;

    //アクセス修飾子は付けられない
    //全部自動でpublicになる
    void Test1();

    //抽象メソッドなので
    //メソッドの定義は持たない
    int Test2(int n);
}

メンバーにアクセス修飾子は指定できず、すべて自動でpublicとなります。
(interface自体にアクセス修飾子を付けることはできます)

抽象クラスの抽象メソッドと同じく、メソッドの定義は持ちません。
abstractキーワードは記述しません。

インターフェイスは、それを継承したクラスが「最低限できること」を保障するためのものです。
上のサンプルコードでは、このインターフェイスから派生したクラスは最低限「Test1」と「Test2」メソッドを持っていることが保障されます。
通常のクラスの継承でも同じことはできますが、それ以外の余計なものを排除し、それに特化した機能だけを提供するのがインターフェイスです。
ちなみにこの関係はcan-do関係といいます。
(B can do A)

ただし、インターフェイスではメソッドの実際の動作は定義しませんので、派生クラスでオーバーライドして動作を定義する必要があります。
インターフェイスを継承しただけでその機能が使えるわけではありません。
実際にどのような動作をさせるかはプログラマの責任となります。

インターフェイスを利用する派生クラス側では、抽象クラスの時とは違いoverrideキーワードは付けません。


interface ITest
{
    void Test1();
    int Test2(int n);
}

class Test : ITest
{
    //overideキーワードは付けない
    public void Test1()
    {
        Console.WriteLine("Test1");
    }

    public int Test2(int n)
    {
        return n;
    }

    //実装は適当
}

多重継承が可能

通常の継承の場合、基底クラスに持てるのは常にひとつだけですが、インターフェイスを継承する場合はいくつでも継承元に指定できます。
基底クラスとインターフェイスを同時に継承元に指定する場合、基底クラスから先に記述します。


class Base
{
}

interface ITest1
{
}

interface ITest2
{
}

//基底クラスはひとつまで
//インターフェイスはいくつでも指定可能
class Derived : Base, ITest1, ITest2
{
}

インターフェイスの使用例

インターフェイスを実際に利用したサンプルコードを以下に示します。

ICloneableインターフェイスはCloneメソッドの実装を要求します。
IComparableインターフェイスはCompareToメソッドの実装を要求します。
どちらも.NET標準で用意されているインターフェイスです。

インターフェイス内で定義されているメソッド名や引数、戻り値を知りたい場合は、Visual Studioのコードエディタ上でインターフェイス名にキャレットを合わせて「F12」キーを押下するか、右クリックで「定義に移動」を選択すれば確認することができます。
定義の確認

以下のICloneable、IComparableインターフェイスは.NET内部での定義を抜粋したものです。
ソースコードに記述する必要はありません。
ComVisible属性は今は無関係なので気にしなくて良いです。


namespace System
{
    [ComVisible(true)]
    public interface ICloneable
    {
        object Clone();
    }
}

namespace System
{
    [ComVisible(true)]
    public interface IComparable
    {
        int CompareTo(object obj);
    }
}

abstract class Person
{
    public string Name { get; protected set; }
    public int Age { get; protected set; }

    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }

    //自己紹介する抽象メソッド
    public abstract void Introduce();
}

//基底クラスはひとつまで、
//インターフェイスはいくつでも継承できる
//Introduce、Clone、CompareToメソッドの実装が必要
class Student : Person, ICloneable, IComparable
{
    public int StudentNumber { get; set; }

    public Student(string name, int age, int studentNumber)
        : base(name, age)
    {
        StudentNumber = studentNumber;
    }

    public override void Introduce()
    {
        Console.WriteLine(
            "私は{0}、{1}歳、出席番号は{2}番です。",
            Name, Age, StudentNumber);
    }

    //オブジェクトのクローン(複製)を作る
    public object Clone()
    {
        return new Student(Name, Age, StudentNumber);
    }

    //大小を比較する
    //今回はStudentNumberの大小を比較
    public int CompareTo(object obj)
    {
        if (obj is Student)
        {
            return StudentNumber.CompareTo(((Student)obj).StudentNumber);
        }
        //Studentクラス以外が渡された場合は「大きい」と判断
        return 1;
    }
}

//Personクラスとは無関係なクラス
//CompareToメソッドの実装が必要
class Car : IComparable
{
    string model;
    int price;

    public Car(string model, int price)
    {
        this.model = model;
        this.price = price;
    }

    //priceで比較
    public int CompareTo(object obj)
    {
        if (obj is Car)
        {
            return price.CompareTo(((Car)obj).price);
        }
        //Carクラス以外が渡された場合は「大きい」と判断
        return 1;
    }
}

static int Compare(IComparable c1, IComparable c2)
{
    //引数のオブジェクトは
    //CompareToメソッドを持っていることが
    //保障されている
    return c1.CompareTo(c2);
}

static void Main(string[] args)
{
    Student s1 = new Student("A山B太", 16, 5);
    Student s2 = new Student("C谷D男", 17, 9);

    int order = Compare(s1, s2);

    string sign = order == 0 ? "=" :
        order > 0 ? ">" : "<";
    Console.WriteLine("s1 {0} s2", sign);

    //Cloneメソッドはobject型を返すので
    //目的の型にキャストする
    Student s3 = (Student)s1.Clone();

    //複製したオブジェクトの中身確認
    s3.Introduce();

    Car c1 = new Car("セダン", 1190000);
    Car c2 = new Car("ライトバン", 780000);

    //Compareメソッドは
    //IComparableインターフェイスを
    //継承するクラスなら何でもOK
    order = Compare(c1, c2);

    sign = order == 0 ? "=" :
        order > 0 ? ">" : "<";
    Console.WriteLine("c1 {0} c2", sign);

    Console.ReadLine();
}
s1 < s2
私はA山B太、16歳、出席番号は5番です。
c1 > c2

自作メソッドのCompareはIComparable型をふたつ引数に取ります。
このメソッドはIComparableを継承したクラスならばどんなクラスでも比較することができます。
ただし、比較のためのルールはクラス側できちんと定義する必要があります。