インターフェースとは
インターフェースの概念
抽象クラスの概念をさらに推し進めたのが、インターフェースという概念です。インターフェースもインスタンスを生成できませんが、抽象クラスとは根本的に異なります。インターフェースは、メソッドの実装やフィールドを一切持つことができません。メソッドの「呼び出し形式(シグネチャ)」のみを定義します。
インターフェースは「1つのクラスが複数の側面を持つ場合」によく使われます。例えば、携帯電話は「電話機能」と「メール機能」の両方を持っています。このような場合にインターフェースが有効です。
インターフェースの定義
{
(メソッドのシグネチャ);
…
}
インターフェース内のメソッドは public であることが前提なので、アクセス修飾子を付ける必要はありません。また、abstract や override も不要です。命名慣例として、インターフェース名の先頭に I を付けます(例:IPhone、IEmail)。
インターフェースの実装
{
…
}
クラスは複数のインターフェースを同時に実装できます(カンマ区切り)。これは C# の単一継承の制約を超えて「複数の型として振る舞う」ことを可能にします。
サンプルプログラム
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SampleEx501
{
interface IPhone
{
void Call(string number);
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SampleEx501
{
interface IEmail
{
void SendMail(string address);
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SampleEx501
{
class CellPhone : IPhone, IEmail
{
private string mailAddress;
private string number;
public CellPhone(string mailAddress, string number)
{
this.mailAddress = mailAddress;
this.number = number;
}
public void Call(string number)
{
Console.WriteLine(number + "に、" + this.number + "から電話をかけます。");
}
public void SendMail(string address)
{
Console.WriteLine(address + "に、" + this.mailAddress + "からメールを出します。");
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SampleEx501
{
class Program
{
static void Main(string[] args)
{
CellPhone cp = new CellPhone("hoge@email.com", "090-1234-5678");
cp.Call("011-123-4567");
cp.SendMail("fuga@email.com");
// IPhone 型にキャストすると Call() しか使えなくなる
IPhone phone = (IPhone)cp;
phone.Call("011-987-6543");
// IEmail 型にキャストすると SendMail() しか使えなくなる
IEmail mail = (IEmail)cp;
mail.SendMail("bar@email.com");
}
}
}
fuga@email.comに、hoge@email.comからメールを出します。
011-987-6543に、090-1234-5678から電話をかけます。
bar@email.comに、hoge@email.comからメールを出します。
インターフェースとキャスト
インターフェース型の変数にキャストすると、もとは同じクラスのインスタンスであっても、キャストした型で定義されているメンバしか使用できなくなります。例えば、IPhone 型にキャストした phone は Call() のみ呼び出せます。これにより、大規模開発でクラスの機能を意図的に制限し、余計なメンバへのアクセスを防ぐことができます。
複数インターフェースの実装
サンプルプログラム
次のサンプルでは、1つのクラスが2つのインターフェースを実装しており、両方に共通するメソッド名が含まれています。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SampleEx502
{
interface IFuncs1
{
void Func1();
void Func2();
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SampleEx502
{
interface IFuncs2
{
void Func2();
void Func3();
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SampleEx502
{
class Dummy : IFuncs1, IFuncs2
{
public void Func1()
{
Console.WriteLine("Func1");
}
public void Func2()
{
Console.WriteLine("Func2");
}
public void Func3()
{
Console.WriteLine("Func3");
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SampleEx502
{
class Program
{
static void Main(string[] args)
{
Dummy d = new Dummy();
IFuncs1 i1 = (IFuncs1)d;
IFuncs2 i2 = (IFuncs2)d;
i1.Func1();
i1.Func2();
i2.Func2();
i2.Func3();
}
}
}
Func2
Func2
Func3
インターフェースと抽象クラスの違い
IFuncs1 と IFuncs2 の両方に Func2() が含まれていますが、Dummy クラスでは1つの実装で対応できます。インターフェースは「クラスの一部を切り取るためのもの」であり、メソッドの重複があってもかまいません。
| 項目 | 抽象クラス | インターフェース |
|---|---|---|
| フィールド | 持てる | 持てない |
| メソッドの実装 | 持てる(一部) | 持てない |
| 多重継承 | 不可(単一のみ) | 複数実装可 |
| 目的 | 共通機能の集約と拡張 | クラスの機能の切り取り・制限 |