C#のinterfaceとは?使い方・メリット・抽象クラスとの違いを初心者向けに徹底解説
はじめに
C#を学び始めると、クラス、継承、抽象クラスと並んでよく出てくるのがinterfaceです。
interfaceは、C#で保守しやすいコードを書くうえで非常に重要な仕組みです。特に、実務のC#開発では、Webアプリケーション、業務システム、ゲーム開発、単体テスト、DIコンテナなど、さまざまな場面でinterfaceが使われます。
一方で、初心者にとっては次のような疑問も出やすいです。
「interfaceとは何か」
「クラスと何が違うのか」
「抽象クラスとどう使い分ければよいのか」
「なぜわざわざinterfaceを使う必要があるのか」
この記事では、C#のinterfaceについて、基本構文からメリット、抽象クラスとの違い、実務での使い分け、初心者がつまずきやすいエラーまで順番に解説します。
1. C#のinterfaceとは?初心者向けにわかりやすく解説
C#のinterfaceとは、クラスや構造体が「どのような機能を持つべきか」を定義するための仕組みです。
日本語では「インターフェイス」または「インターフェース」と表記されます。C#のコードではinterfaceキーワードを使って定義します。
たとえば、動物を表すクラスに「鳴く」という機能を持たせたい場合、次のようなinterfaceを作れます。
C#public interface IAnimal
{
void Speak();
}
このIAnimalは、「IAnimalを実装するクラスは、必ずSpeakメソッドを持ってください」というルールを表します。
1-1. interfaceは「クラスが必ず実装する機能を決める契約」
interfaceを理解するときは、「契約」という言葉で考えるとわかりやすいです。
interfaceは、クラスに対して「このメソッドを持つこと」「このプロパティを持つこと」といった約束を決めます。MicrosoftのC#ドキュメントでも、interfaceはクラスや構造体が実装すべきメソッド、プロパティ、イベント、インデクサーなどのまとまりを定義する「contract」と説明されています。
たとえば、次のようなinterfaceがあるとします。
C#public interface IShape
{
double GetArea();
}
このIShapeを実装するクラスは、必ずGetAreaメソッドを実装しなければなりません。
C#public class Circle : IShape
{
public double Radius { get; set; }
public double GetArea()
{
return Radius * Radius * Math.PI;
}
}
CircleクラスはIShapeを実装しているため、「面積を取得できる図形」として扱えます。
1-2. interfaceで定義できるもの:メソッド・プロパティ・イベント・インデクサー
C#のinterfaceでは、主に次のようなメンバーを定義できます。
| 定義できるもの | 例 | 用途 |
|---|---|---|
| メソッド | void Save(); | 処理の実行 |
| プロパティ | string Name { get; set; } | 値の取得・設定 |
| イベント | event EventHandler Completed; | 何かが起きたことの通知 |
| インデクサー | string this[int index] { get; } | 配列のようなアクセス |
MicrosoftのC#リファレンスでは、interface宣言にメソッド、プロパティ、インデクサー、イベントなどを含められることが説明されています。現在のC#では、定数、演算子、静的メンバー、既定実装なども扱えるようになっていますが、初心者のうちはまず「メソッドやプロパティのルールを決めるもの」と理解するとよいでしょう。
基本的な例は次のとおりです。
C#public interface IUser
{
int Id { get; }
string Name { get; set; }
void Login();
void Logout();
}
このinterfaceを実装するクラスは、Id、Name、Login、Logoutを持つ必要があります。
1-3. interface自体はインスタンス化できない
interfaceは「設計図」や「契約」のようなものであり、具体的なオブジェクトそのものではありません。そのため、interface自体をnewしてインスタンス化することはできません。
次のコードはエラーになります。
C#IAnimal animal = new IAnimal(); // エラー
IAnimalはinterfaceであり、具体的な処理を持つクラスではないためです。
正しくは、interfaceを実装したクラスをインスタンス化します。
C#IAnimal animal = new Dog();
このように、変数の型をinterfaceにし、実際の中身として実装クラスを代入する使い方は、C# interfaceの重要なポイントです。
1-4. クラス・構造体がinterfaceを実装する仕組み
C#では、クラスや構造体がinterfaceを実装できます。
クラスでinterfaceを実装する場合は、クラス名の後ろにコロン:を書き、その後にinterface名を指定します。
C#public interface ILogger
{
void Log(string message);
}
public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine(message);
}
}
この例では、ConsoleLoggerクラスがILoggerを実装しています。
ILoggerにはLogメソッドが定義されているため、ConsoleLoggerクラスはpublic void Log(string message)を実装する必要があります。
構造体でも同じようにinterfaceを実装できます。
C#public struct Point : IPrintable
{
public int X { get; set; }
public int Y { get; set; }
public void Print()
{
Console.WriteLine($"X={X}, Y={Y}");
}
}
C#ではクラスの多重継承はできませんが、複数のinterfaceを実装できます。これは、interfaceが設計でよく使われる大きな理由のひとつです。
1-5. C#でinterfaceがよく使われる場面
C#のinterfaceは、次のような場面でよく使われます。
| 場面 | 例 |
|---|---|
| 複数のクラスを同じ型として扱いたい | IAnimalとしてDogやCatを扱う |
| 実装を差し替えたい | ILoggerをConsoleLoggerやFileLoggerに変更する |
| 単体テストを書きやすくしたい | 本物のDB接続をモックに差し替える |
| DIを使いたい | IUserRepositoryをコンストラクターで受け取る |
| 標準ライブラリを活用したい | IEnumerable<T>、IDisposableなど |
たとえば、ログ出力の処理をinterfaceにしておくと、コンソール出力、ファイル出力、データベース出力などを簡単に差し替えられます。
C#public interface ILogger
{
void Log(string message);
}
このように、C# interfaceは「実装の詳細に依存しないコード」を書くために使われます。
2. C#でinterfaceを使う基本構文
ここからは、C#でinterfaceを使う基本構文を見ていきましょう。
interfaceは難しそうに見えますが、構文自体はシンプルです。
2-1. interfaceの定義方法
interfaceはinterfaceキーワードを使って定義します。
C#public interface IInterfaceName
{
void MethodName();
}
たとえば、通知を送信する機能を表すinterfaceは次のように書けます。
C#public interface INotifier
{
void Send(string message);
}
このinterfaceは、「通知を送る機能を持つクラスはSendメソッドを実装してください」という意味になります。
interface内のメソッドは、基本的には処理の中身を書きません。
C#public interface INotifier
{
void Send(string message); // 処理の中身は書かない
}
クラス側で具体的な処理を書きます。
C#public class EmailNotifier : INotifier
{
public void Send(string message)
{
Console.WriteLine($"メール送信: {message}");
}
}
2-2. interface名に「I」を付ける命名規則
C#では、interface名の先頭にIを付ける命名が一般的です。
たとえば次のような名前です。
C#public interface ILogger
{
}
public interface IRepository
{
}
public interface IDisposable
{
}
これはC#や.NETの標準ライブラリでもよく使われている命名規則です。
| interface名 | 意味 |
|---|---|
IAnimal | 動物として扱える |
ILogger | ログ出力できる |
IRepository | データ操作できる |
IPrintable | 印刷できる |
IEnumerable<T> | 列挙できる |
必ずコンパイルに必要なルールではありませんが、C#ではIから始まる名前を見ると「これはinterfaceだ」とすぐにわかるため、読みやすいコードになります。
2-3. クラスでinterfaceを実装する方法
クラスでinterfaceを実装するには、クラス名の後ろに: インターフェイス名を書きます。
C#public interface IWorker
{
void Work();
}
public class Engineer : IWorker
{
public void Work()
{
Console.WriteLine("エンジニアが働いています");
}
}
この例では、EngineerクラスがIWorkerinterfaceを実装しています。
interfaceのメンバーを実装するときは、通常publicを付けます。
C#public void Work()
{
Console.WriteLine("作業します");
}
publicを付け忘れると、interfaceのメンバーを正しく実装していないと判断されることがあります。
2-4. interfaceメンバーの実装例
メソッドだけでなく、プロパティもinterfaceに定義できます。
C#public interface IProduct
{
string Name { get; set; }
decimal Price { get; set; }
void Display();
}
これを実装するクラスは次のようになります。
C#public class Book : IProduct
{
public string Name { get; set; }
public decimal Price { get; set; }
public void Display()
{
Console.WriteLine($"{Name}: {Price}円");
}
}
interfaceに定義されたプロパティは、クラス側で実装する必要があります。
C#var book = new Book
{
Name = "C#入門",
Price = 2500
};
book.Display();
実行結果は次のようになります。
C#入門: 2500円
2-5. 複数のinterfaceを実装する方法
C#のクラスは、複数のinterfaceを実装できます。
C#public interface IPrintable
{
void Print();
}
public interface ISavable
{
void Save();
}
public class Report : IPrintable, ISavable
{
public void Print()
{
Console.WriteLine("レポートを印刷します");
}
public void Save()
{
Console.WriteLine("レポートを保存します");
}
}
Reportクラスは、IPrintableとISavableの両方を実装しています。
つまり、Reportは「印刷できる」かつ「保存できる」クラスです。
C#var report = new Report();
report.Print();
report.Save();
C#ではクラスを複数継承することはできませんが、interfaceは複数実装できます。これにより、「多重継承のような設計」が可能になります。
2-6. interfaceを型として扱う方法
interfaceの重要な特徴は、「型」として使えることです。
C#ILogger logger = new ConsoleLogger();
logger.Log("処理を開始しました");
このコードでは、変数の型はILoggerですが、実際に入っているオブジェクトはConsoleLoggerです。
C#public interface ILogger
{
void Log(string message);
}
public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine(message);
}
}
このようにinterface型で扱うことで、呼び出し側は具体的なクラス名を意識しなくてよくなります。
たとえば後からFileLoggerに差し替えることもできます。
C#public class FileLogger : ILogger
{
public void Log(string message)
{
File.WriteAllText("log.txt", message);
}
}
呼び出し側がILoggerだけに依存していれば、ConsoleLoggerでもFileLoggerでも同じように扱えます。
3. C#のinterfaceの使い方をコード例で理解する
ここでは、C# interfaceの使い方を具体的なコード例で確認します。
初心者のうちは、説明だけを読むよりも、実際のコードを見たほうが理解しやすくなります。
3-1. 基本例:IAnimalを作ってDogクラスに実装する
まずは、もっとも基本的な例です。
C#public interface IAnimal
{
void Speak();
}
IAnimalは、「鳴くことができる動物」を表すinterfaceです。
次に、Dogクラスでこのinterfaceを実装します。
C#public class Dog : IAnimal
{
public void Speak()
{
Console.WriteLine("ワンワン");
}
}
使う側のコードは次のとおりです。
C#IAnimal animal = new Dog();
animal.Speak();
実行結果は次のようになります。
ワンワン
ポイントは、変数の型がDogではなくIAnimalになっていることです。
C#IAnimal animal = new Dog();
これにより、「Dogクラスそのもの」ではなく、「IAnimalとして扱えるもの」としてコードを書けます。
3-2. プロパティを持つinterfaceの実装例
interfaceにはプロパティも定義できます。
C#public interface IUser
{
int Id { get; }
string Name { get; set; }
}
このinterfaceを実装するクラスは、IdとNameを持つ必要があります。
C#public class User : IUser
{
public int Id { get; private set; }
public string Name { get; set; }
public User(int id, string name)
{
Id = id;
Name = name;
}
}
使い方は次のとおりです。
C#IUser user = new User(1, "田中");
Console.WriteLine(user.Id);
Console.WriteLine(user.Name);
実行結果は次のようになります。
1
田中
IUserではIdはgetのみですが、実装クラス側ではprivate setを付けています。これは、「外部からは読み取り専用だが、クラス内部では設定できる」という設計です。
3-3. 複数クラスで同じinterfaceを実装する例
interfaceの強みは、複数のクラスに同じルールを適用できることです。
C#public interface IAnimal
{
void Speak();
}
DogクラスとCatクラスで同じinterfaceを実装してみます。
C#public class Dog : IAnimal
{
public void Speak()
{
Console.WriteLine("ワンワン");
}
}
public class Cat : IAnimal
{
public void Speak()
{
Console.WriteLine("ニャー");
}
}
どちらのクラスもIAnimalを実装しているため、同じ型として扱えます。
C#IAnimal dog = new Dog();
IAnimal cat = new Cat();
dog.Speak();
cat.Speak();
実行結果は次のようになります。
ワンワン
ニャー
同じSpeakメソッドを呼び出しているのに、実際の動作はクラスごとに異なります。これがinterfaceを使ったポリモーフィズムの基本です。
3-4. interface型の変数に実装クラスを代入する例
interface型の変数には、そのinterfaceを実装したクラスのインスタンスを代入できます。
C#public interface IPayment
{
void Pay(decimal amount);
}
public class CreditCardPayment : IPayment
{
public void Pay(decimal amount)
{
Console.WriteLine($"クレジットカードで{amount}円支払いました");
}
}
public class CashPayment : IPayment
{
public void Pay(decimal amount)
{
Console.WriteLine($"現金で{amount}円支払いました");
}
}
使う側は、IPayment型として扱えます。
C#IPayment payment = new CreditCardPayment();
payment.Pay(3000);
payment = new CashPayment();
payment.Pay(1500);
実行結果は次のようになります。
クレジットカードで3000円支払いました
現金で1500円支払いました
このように、同じIPayment型の変数に異なる実装クラスを代入できます。
これにより、「支払い方法が変わっても、呼び出し側のコードを大きく変えなくてよい」設計になります。
3-5. foreachやリストでinterfaceを活用する例
interfaceを使うと、複数の異なるクラスを同じリストで扱えます。
C#var animals = new List<IAnimal>
{
new Dog(),
new Cat()
};
foreach (IAnimal animal in animals)
{
animal.Speak();
}
実行結果は次のようになります。
ワンワン
ニャー
DogとCatは別々のクラスですが、どちらもIAnimalを実装しているため、List<IAnimal>に入れられます。
実務でもこの考え方はよく使われます。
たとえば、複数の通知方法をまとめて処理したい場合です。
C#public interface INotifier
{
void Send(string message);
}
public class EmailNotifier : INotifier
{
public void Send(string message)
{
Console.WriteLine($"メール: {message}");
}
}
public class SmsNotifier : INotifier
{
public void Send(string message)
{
Console.WriteLine($"SMS: {message}");
}
}
C#var notifiers = new List<INotifier>
{
new EmailNotifier(),
new SmsNotifier()
};
foreach (var notifier in notifiers)
{
notifier.Send("注文が完了しました");
}
このようにinterfaceを使うと、「通知する」という共通の操作だけに注目して処理を書けます。
3-6. 明示的インターフェイス実装の基本
C#には、明示的インターフェイス実装という書き方があります。
これは、複数のinterfaceに同じ名前のメンバーがある場合や、interface経由でのみ呼び出したい場合に使います。
C#public interface IPrintable
{
void Execute();
}
public interface ISavable
{
void Execute();
}
public class Document : IPrintable, ISavable
{
void IPrintable.Execute()
{
Console.WriteLine("印刷します");
}
void ISavable.Execute()
{
Console.WriteLine("保存します");
}
}
使うときは、interface型にキャストして呼び出します。
C#var document = new Document();
IPrintable printable = document;
printable.Execute();
ISavable savable = document;
savable.Execute();
実行結果は次のようになります。
印刷します
保存します
明示的インターフェイス実装では、メソッドにpublicを付けません。Microsoftのコンパイラーメッセージ解説でも、明示的インターフェイス実装にpublic修飾子を付けるとエラーになることが説明されています。
初心者のうちは、まず通常の実装を覚えれば十分です。明示的インターフェイス実装は、「同じ名前のメソッドが衝突したときに使う」と理解しておきましょう。
4. C#でinterfaceを使うメリット
interfaceは、単に「メソッド名を決めるだけ」の機能ではありません。
C#でinterfaceを使う大きなメリットは、コードを柔軟にし、保守しやすくすることです。
4-1. クラス同士の依存を減らせる
interfaceを使うと、クラス同士の依存を減らせます。
たとえば、次のようにOrderServiceがConsoleLoggerに直接依存しているとします。
C#public class OrderService
{
private readonly ConsoleLogger _logger = new ConsoleLogger();
public void Order()
{
_logger.Log("注文処理を開始します");
}
}
このコードでは、ログ出力の方法がConsoleLoggerに固定されています。
もしファイルにログを出したくなった場合、OrderServiceの中身を変更しなければなりません。
そこでinterfaceを使います。
C#public interface ILogger
{
void Log(string message);
}
public class OrderService
{
private readonly ILogger _logger;
public OrderService(ILogger logger)
{
_logger = logger;
}
public void Order()
{
_logger.Log("注文処理を開始します");
}
}
OrderServiceはConsoleLoggerではなくILoggerに依存するようになりました。
これにより、具体的なログ出力方法を外部から差し替えられます。
4-2. 実装を差し替えやすくなる
interfaceを使うと、実装クラスを簡単に差し替えられます。
C#public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine(message);
}
}
public class FileLogger : ILogger
{
public void Log(string message)
{
File.WriteAllText("log.txt", message);
}
}
使う側はILoggerだけを見ています。
C#ILogger logger = new ConsoleLogger();
// ILogger logger = new FileLogger();
var service = new OrderService(logger);
service.Order();
ConsoleLoggerからFileLoggerに変更しても、OrderServiceのコードは変わりません。
これは実務で非常に重要です。開発中はコンソールにログを出し、本番環境ではファイルやクラウドにログを出す、といった切り替えがしやすくなります。
4-3. テストしやすいコードを書ける
interfaceは単体テストでも役立ちます。
たとえば、ユーザー情報をデータベースから取得するクラスがあるとします。
C#public interface IUserRepository
{
User FindById(int id);
}
本番用の実装はデータベースに接続します。
C#public class DatabaseUserRepository : IUserRepository
{
public User FindById(int id)
{
// 実際にはデータベースから取得する
return new User(id, "本番ユーザー");
}
}
テストでは、データベースに接続したくない場合があります。そのときは、テスト用の実装を作れます。
C#public class FakeUserRepository : IUserRepository
{
public User FindById(int id)
{
return new User(id, "テストユーザー");
}
}
サービス側はIUserRepositoryだけに依存します。
C#public class UserService
{
private readonly IUserRepository _repository;
public UserService(IUserRepository repository)
{
_repository = repository;
}
public string GetUserName(int id)
{
var user = _repository.FindById(id);
return user.Name;
}
}
テスト時にはFakeUserRepositoryを渡せます。
C#var service = new UserService(new FakeUserRepository());
string name = service.GetUserName(1);
Console.WriteLine(name);
このように、interfaceを使うとテストしやすい設計になります。
4-4. 複数のクラスを同じルールで扱える
interfaceを使うと、異なるクラスを同じルールで扱えます。
C#public interface IExportable
{
void Export();
}
public class PdfReport : IExportable
{
public void Export()
{
Console.WriteLine("PDFとして出力します");
}
}
public class CsvReport : IExportable
{
public void Export()
{
Console.WriteLine("CSVとして出力します");
}
}
どちらのクラスもIExportableとして扱えます。
C#var reports = new List<IExportable>
{
new PdfReport(),
new CsvReport()
};
foreach (var report in reports)
{
report.Export();
}
この設計にしておけば、新しくExcelReportを追加しても、既存の処理を大きく変える必要がありません。
C#public class ExcelReport : IExportable
{
public void Export()
{
Console.WriteLine("Excelとして出力します");
}
}
4-5. 多重継承のような設計ができる
C#では、クラスは1つのクラスからしか継承できません。
C#public class Child : Parent
{
}
しかし、interfaceは複数実装できます。
C#public class MultiFunctionPrinter : IPrintable, IScannable, IFaxable
{
public void Print()
{
Console.WriteLine("印刷します");
}
public void Scan()
{
Console.WriteLine("スキャンします");
}
public void Fax()
{
Console.WriteLine("FAXを送信します");
}
}
この例では、MultiFunctionPrinterは次の3つの機能を持ちます。
| interface | 意味 |
|---|---|
IPrintable | 印刷できる |
IScannable | スキャンできる |
IFaxable | FAXできる |
このように、interfaceを使うと「複数の能力を持つクラス」を自然に表現できます。
4-6. 保守性・拡張性の高い設計につながる
interfaceを使うと、後から機能を追加しやすくなります。
たとえば、支払い処理を次のようにinterface化しておきます。
C#public interface IPaymentService
{
void Pay(decimal amount);
}
最初はクレジットカード決済だけだったとします。
C#public class CreditCardPaymentService : IPaymentService
{
public void Pay(decimal amount)
{
Console.WriteLine("クレジットカードで支払います");
}
}
後からQRコード決済を追加したくなった場合、新しいクラスを追加するだけで対応できます。
C#public class QrPaymentService : IPaymentService
{
public void Pay(decimal amount)
{
Console.WriteLine("QRコードで支払います");
}
}
呼び出し側がIPaymentServiceに依存していれば、既存コードへの影響を小さくできます。
これが、interfaceが保守性・拡張性の高い設計につながる理由です。
5. interfaceと抽象クラスの違い
C# interfaceを学ぶと、多くの人が迷うのが「抽象クラスとの違い」です。
interfaceと抽象クラスはどちらも「直接インスタンス化しにくい設計上の型」として使われますが、役割は異なります。
5-1. interfaceと抽象クラスの役割の違い
interfaceは、「何ができるか」を表します。
C#public interface IRunnable
{
void Run();
}
これは「走ることができる」という能力を表しています。
一方、抽象クラスは「共通の土台」を表します。
C#public abstract class Animal
{
public string Name { get; set; }
public abstract void Speak();
}
これは「動物」という共通のベースを表しています。
| 種類 | 役割 |
|---|---|
| interface | できること、能力、契約を表す |
| 抽象クラス | 共通の土台、共通処理、親クラスを表す |
IRunnableは、動物でもロボットでも車でも実装できます。
一方、Animalを継承するのは基本的に動物に限定されます。
5-2. 実装を持てるかどうかの違い
従来、interfaceは実装を持たず、抽象クラスは実装を持てる、という違いで説明されることが多くありました。
現在のC#では、interfaceにも既定実装を持たせることができます。Microsoftのドキュメントでも、default interface membersによってinterface内に既定実装を定義できることが説明されています。
例を見てみましょう。
C#public interface ILogger
{
void Log(string message);
void LogError(string message)
{
Log($"ERROR: {message}");
}
}
ただし、初心者のうちは「interfaceは基本的にルールを決めるもの」「抽象クラスは共通処理を持てる親クラス」と考えると理解しやすいです。
抽象クラスでは、通常のメソッドと抽象メソッドを混在させられます。
C#public abstract class Animal
{
public string Name { get; set; }
public void Sleep()
{
Console.WriteLine($"{Name}は眠っています");
}
public abstract void Speak();
}
この例では、Sleepは共通処理、Speakは派生クラスに実装を任せる処理です。
5-3. フィールド・コンストラクターを持てるかどうか
抽象クラスは、フィールドやコンストラクターを持てます。
C#public abstract class Animal
{
protected string name;
public Animal(string name)
{
this.name = name;
}
public abstract void Speak();
}
派生クラスは、親クラスのコンストラクターを呼び出せます。
C#public class Dog : Animal
{
public Dog(string name) : base(name)
{
}
public override void Speak()
{
Console.WriteLine($"{name}がワンワンと鳴きます");
}
}
一方、interfaceはインスタンスの状態を持つための通常のフィールドや、インスタンス生成のための通常のコンストラクターを持つ用途には向いていません。
そのため、「共通の状態を持たせたい」「共通の初期化処理を書きたい」という場合は、抽象クラスのほうが適しています。
5-4. 継承できる数の違い
C#では、クラスの継承は1つだけです。
C#public class Dog : Animal
{
}
次のように、複数のクラスを同時に継承することはできません。
C#public class Dog : Animal, Machine // エラー
{
}
一方で、interfaceは複数実装できます。
C#public class RobotDog : IAnimal, IRobot, IRunnable
{
public void Speak()
{
Console.WriteLine("電子音で鳴きます");
}
public void Charge()
{
Console.WriteLine("充電します");
}
public void Run()
{
Console.WriteLine("走ります");
}
}
この違いは非常に重要です。
| 種類 | 複数指定 |
|---|---|
| クラス継承 | 不可 |
| interface実装 | 可能 |
複数の能力を組み合わせたい場合は、interfaceが適しています。
5-5. アクセス修飾子の考え方の違い
抽象クラスでは、通常のクラスと同じようにアクセス修飾子を使えます。
C#public abstract class Animal
{
public string Name { get; set; }
protected int Age { get; set; }
private string Secret { get; set; }
public abstract void Speak();
}
一方、interfaceでは、基本的な抽象メンバーは公開される契約として扱われます。C#のリファレンスでは、interfaceの抽象メソッドの既定アクセスはpublicであることが説明されています。
そのため、通常のinterface実装では、クラス側の実装メンバーにpublicを付けます。
C#public interface IAnimal
{
void Speak();
}
public class Dog : IAnimal
{
public void Speak()
{
Console.WriteLine("ワンワン");
}
}
次のようにpublicを付けないと、正しく実装したことにならない場合があります。
C#public class Dog : IAnimal
{
void Speak() // エラーになりやすい
{
Console.WriteLine("ワンワン");
}
}
明示的インターフェイス実装では例外的にpublicを付けません。
C#public class Dog : IAnimal
{
void IAnimal.Speak()
{
Console.WriteLine("ワンワン");
}
}
5-6. interfaceと抽象クラスの違いを比較表で整理
interfaceと抽象クラスの違いを表で整理すると、次のようになります。
| 比較項目 | interface | 抽象クラス |
|---|---|---|
| 主な役割 | 契約・能力を表す | 共通の土台を表す |
| インスタンス化 | できない | できない |
| 実装 | 既定実装は可能だが、基本は契約中心 | 共通処理を持てる |
| フィールド | 通常のインスタンス状態の保持には向かない | 持てる |
| コンストラクター | 通常のインスタンス生成用には使わない | 持てる |
| 複数指定 | 複数実装できる | 1つだけ継承できる |
| 使いどころ | 「できること」を表す | 「共通の親」を表す |
| 例 | ILogger, IEnumerable<T> | Animal, BaseController |
抽象クラスは、MicrosoftのC#リファレンスでも、他のクラスの基底クラスとして使うことを意図し、抽象メンバーを派生クラスで実装させる仕組みとして説明されています。
5-7. 初心者が迷いやすいポイント
初心者が迷いやすいのは、「interfaceにも既定実装を書けるなら、抽象クラスと同じではないか」という点です。
確かに、現在のC#ではinterfaceにも一部の実装を書けます。しかし、設計上の考え方は異なります。
interfaceは、あくまで「この機能を持つ」という契約を表すために使うのが基本です。
C#public interface IPrintable
{
void Print();
}
抽象クラスは、「同じ種類のものに共通する土台」を作るために使います。
C#public abstract class ReportBase
{
public string Title { get; set; }
public void ShowTitle()
{
Console.WriteLine(Title);
}
public abstract void Print();
}
迷ったときは、次のように考えると判断しやすくなります。
| 判断基準 | 選ぶもの |
|---|---|
| 複数の無関係なクラスに同じ機能を持たせたい | interface |
| 共通の状態や処理を親クラスにまとめたい | 抽象クラス |
| 複数の型に能力を追加したい | interface |
| 継承関係として自然に表現したい | 抽象クラス |
6. interfaceと抽象クラスはどう使い分ける?
ここでは、interfaceと抽象クラスの使い分けをさらに具体的に見ていきます。
6-1. interfaceを使うべきケース
interfaceを使うべきなのは、「何ができるか」を表したいときです。
たとえば、次のようなケースです。
| 使いたい場面 | interface例 |
|---|---|
| ログを出力できる | ILogger |
| 保存できる | ISavable |
| 印刷できる | IPrintable |
| 通知できる | INotifier |
| 支払い処理ができる | IPaymentService |
| データを取得できる | IRepository |
たとえば、ログ出力の機能は、コンソール、ファイル、クラウドなど、さまざまな実装が考えられます。
C#public interface ILogger
{
void Log(string message);
}
実装クラスは複数作れます。
C#public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine(message);
}
}
public class FileLogger : ILogger
{
public void Log(string message)
{
File.WriteAllText("log.txt", message);
}
}
このように、「やることは同じだが、具体的なやり方が違う」場合はinterfaceが向いています。
6-2. 抽象クラスを使うべきケース
抽象クラスを使うべきなのは、「共通の状態や処理を持つ親クラス」を作りたいときです。
たとえば、帳票の共通処理をまとめたい場合です。
C#public abstract class ReportBase
{
public string Title { get; set; }
public void PrintHeader()
{
Console.WriteLine($"=== {Title} ===");
}
public abstract void PrintBody();
}
派生クラスは共通処理を利用しつつ、個別の処理だけ実装します。
C#public class SalesReport : ReportBase
{
public override void PrintBody()
{
Console.WriteLine("売上レポートの本文を出力します");
}
}
抽象クラスが向いているのは、次のような場合です。
| 場面 | 理由 |
|---|---|
| 共通フィールドを持ちたい | 抽象クラスは状態を持てる |
| 共通コンストラクターを使いたい | 初期化処理を書ける |
| 一部だけ派生クラスに任せたい | 抽象メソッドを使える |
| 同じ種類のクラスをまとめたい | 継承関係が自然 |
6-3. 「できること」を表すならinterface
interfaceは「できること」を表すのに向いています。
たとえば、次のような名前はinterfaceらしい名前です。
C#public interface IReadable
{
string Read();
}
public interface IWritable
{
void Write(string text);
}
IReadableは「読める」、IWritableは「書ける」という能力を表します。
この考え方なら、ファイル、ネットワーク、メモリ上のデータなど、さまざまなクラスに同じinterfaceを実装できます。
C#public class TextFile : IReadable, IWritable
{
public string Read()
{
return "ファイルから読み込み";
}
public void Write(string text)
{
Console.WriteLine($"ファイルに書き込み: {text}");
}
}
能力を表す場合は、クラスの種類が違っても実装できます。
6-4. 「共通の土台」を作るなら抽象クラス
抽象クラスは「共通の土台」を作るのに向いています。
C#public abstract class Employee
{
public string Name { get; set; }
public decimal BaseSalary { get; set; }
public abstract decimal CalculateSalary();
}
Employeeは、社員という共通の土台です。
C#public class FullTimeEmployee : Employee
{
public override decimal CalculateSalary()
{
return BaseSalary;
}
}
public class PartTimeEmployee : Employee
{
public int Hours { get; set; }
public decimal HourlyRate { get; set; }
public override decimal CalculateSalary()
{
return Hours * HourlyRate;
}
}
FullTimeEmployeeもPartTimeEmployeeも、どちらも社員の一種です。
このように、「AはBの一種である」と自然に言える場合は、抽象クラスが向いています。
6-5. 実務でよくある使い分け例
実務では、interfaceと抽象クラスは次のように使い分けられます。
| 目的 | よく使うもの | 例 |
|---|---|---|
| DIで依存を差し替えたい | interface | IUserService |
| テストでモックにしたい | interface | IRepository |
| 複数実装を切り替えたい | interface | IPaymentService |
| 共通処理をまとめたい | 抽象クラス | BaseController |
| テンプレート処理を作りたい | 抽象クラス | ReportBase |
| 共通状態を持たせたい | 抽象クラス | EntityBase |
たとえば、ASP.NET Coreなどのアプリケーションでは、サービスやリポジトリをinterfaceで定義し、DIで実装を注入する設計がよく使われます。
C#public interface IUserService
{
User GetUser(int id);
}
C#public class UserService : IUserService
{
public User GetUser(int id)
{
return new User(id, "山田");
}
}
一方で、共通のプロパティを持つエンティティには抽象クラスを使うことがあります。
C#public abstract class EntityBase
{
public int Id { get; set; }
public DateTime CreatedAt { get; set; }
}
6-6. interfaceと抽象クラスを組み合わせる設計例
interfaceと抽象クラスは、どちらか一方だけを使うものではありません。組み合わせて使うこともよくあります。
たとえば、通知機能を表すinterfaceを作ります。
C#public interface INotifier
{
void Send(string message);
}
次に、通知クラス共通の処理を抽象クラスにまとめます。
C#public abstract class NotifierBase : INotifier
{
public string SenderName { get; set; }
public void Send(string message)
{
string formattedMessage = FormatMessage(message);
SendCore(formattedMessage);
}
protected string FormatMessage(string message)
{
return $"[{SenderName}] {message}";
}
protected abstract void SendCore(string message);
}
具体的な通知方法は派生クラスで実装します。
C#public class EmailNotifier : NotifierBase
{
protected override void SendCore(string message)
{
Console.WriteLine($"メール送信: {message}");
}
}
public class SmsNotifier : NotifierBase
{
protected override void SendCore(string message)
{
Console.WriteLine($"SMS送信: {message}");
}
}
この設計では、外部からはINotifierとして扱い、内部ではNotifierBaseで共通処理を再利用しています。
C#INotifier notifier = new EmailNotifier
{
SenderName = "システム"
};
notifier.Send("処理が完了しました");
このように、interfaceと抽象クラスを組み合わせると、柔軟性と共通処理の再利用を両立できます。
7. interfaceを理解するための重要概念
C# interfaceを本当に理解するには、いくつかの重要なオブジェクト指向の考え方も押さえておく必要があります。
7-1. ポリモーフィズムとinterfaceの関係
ポリモーフィズムとは、「同じ呼び出し方で、実際の型に応じて異なる動作をする」ことです。
interfaceは、ポリモーフィズムを実現する代表的な方法です。
C#public interface IAnimal
{
void Speak();
}
public class Dog : IAnimal
{
public void Speak()
{
Console.WriteLine("ワンワン");
}
}
public class Cat : IAnimal
{
public void Speak()
{
Console.WriteLine("ニャー");
}
}
同じSpeakメソッドを呼び出しても、実際のクラスによって動作が変わります。
C#List<IAnimal> animals = new List<IAnimal>
{
new Dog(),
new Cat()
};
foreach (var animal in animals)
{
animal.Speak();
}
実行結果は次のようになります。
ワンワン
ニャー
呼び出し側は、DogかCatかを意識していません。
C#animal.Speak();
ただ「IAnimalとしてSpeakできる」とだけ考えています。
これがinterfaceを使ったポリモーフィズムです。
7-2. 疎結合とは何か
疎結合とは、クラス同士の依存関係が弱い状態のことです。
反対に、あるクラスが別の具体的なクラスに強く依存している状態を密結合といいます。
密結合の例です。
C#public class ReportService
{
private readonly PdfExporter _exporter = new PdfExporter();
public void Export()
{
_exporter.Export();
}
}
ReportServiceはPdfExporterに強く依存しています。
CSV出力に変えたい場合、ReportServiceを修正する必要があります。
interfaceを使うと疎結合にできます。
C#public interface IExporter
{
void Export();
}
public class ReportService
{
private readonly IExporter _exporter;
public ReportService(IExporter exporter)
{
_exporter = exporter;
}
public void Export()
{
_exporter.Export();
}
}
これでReportServiceは、具体的なPdfExporterではなくIExporterに依存するようになりました。
C#public class PdfExporter : IExporter
{
public void Export()
{
Console.WriteLine("PDF出力");
}
}
public class CsvExporter : IExporter
{
public void Export()
{
Console.WriteLine("CSV出力");
}
}
疎結合にすることで、変更に強いコードになります。
7-3. 依存性逆転の原則とinterface
依存性逆転の原則とは、SOLID原則のひとつです。
簡単にいうと、「上位の処理が、下位の具体的な実装に直接依存しないようにしましょう」という考え方です。
悪い例を見てみます。
C#public class OrderService
{
private readonly DatabaseOrderRepository _repository;
public OrderService()
{
_repository = new DatabaseOrderRepository();
}
}
OrderServiceはDatabaseOrderRepositoryに直接依存しています。
interfaceを使うと、次のようにできます。
C#public interface IOrderRepository
{
void Save(Order order);
}
public class OrderService
{
private readonly IOrderRepository _repository;
public OrderService(IOrderRepository repository)
{
_repository = repository;
}
}
OrderServiceは、具体的なデータ保存方法ではなく、IOrderRepositoryという抽象に依存しています。
これにより、データベース保存、ファイル保存、テスト用保存などを差し替えやすくなります。
C#public class DatabaseOrderRepository : IOrderRepository
{
public void Save(Order order)
{
Console.WriteLine("データベースに保存");
}
}
public class FakeOrderRepository : IOrderRepository
{
public void Save(Order order)
{
Console.WriteLine("テスト用に保存");
}
}
interfaceは、依存性逆転の原則を実践するためによく使われます。
7-4. DI・モック・単体テストでinterfaceが使われる理由
DIとは、Dependency Injectionの略で、日本語では「依存性の注入」と呼ばれます。
クラスの中で依存オブジェクトを直接作るのではなく、外から渡す設計です。
C#public class UserController
{
private readonly IUserService _userService;
public UserController(IUserService userService)
{
_userService = userService;
}
}
このように、コンストラクターでinterfaceを受け取る形は、C#の実務でよく使われます。
テスト時には、モックやフェイクを渡せます。
C#public class FakeUserService : IUserService
{
public User GetUser(int id)
{
return new User(id, "テストユーザー");
}
}
C#var controller = new UserController(new FakeUserService());
本番では本物の実装を渡し、テストではテスト用の実装を渡せます。
これが、DIや単体テストでinterfaceがよく使われる理由です。
7-5. IEnumerableなど標準ライブラリのinterface例
C#や.NETの標準ライブラリにも、多くのinterfaceがあります。
代表的なものがIEnumerable<T>です。
C#IEnumerable<int> numbers = new List<int> { 1, 2, 3 };
foreach (var number in numbers)
{
Console.WriteLine(number);
}
IEnumerable<T>は、「順番に要素を取り出せる」ことを表すinterfaceです。
List<T>、配列、LINQの結果など、さまざまな型がIEnumerable<T>として扱えます。
ほかにも、IDisposableというinterfaceがあります。
C#using var stream = new FileStream("sample.txt", FileMode.Open);
IDisposableは、使い終わったリソースを解放するためのinterfaceです。
このように、C# interfaceは自分で作るだけでなく、.NET標準ライブラリの中でも広く使われています。
8. C#のinterfaceで初心者がつまずきやすいエラーと対処法
interfaceは便利ですが、初心者がつまずきやすいエラーもあります。
ここでは、よくあるエラーと対処法を紹介します。
8-1. interfaceメンバーを実装していないエラー
よくあるのが、interfaceに定義されたメンバーをクラス側で実装していないエラーです。
C#public interface IAnimal
{
void Speak();
}
public class Dog : IAnimal
{
}
このコードでは、DogクラスがIAnimalを実装しているにもかかわらず、Speakメソッドを実装していません。
正しくは次のように書きます。
C#public class Dog : IAnimal
{
public void Speak()
{
Console.WriteLine("ワンワン");
}
}
interfaceを実装したら、定義されたメンバーをすべて実装する必要があります。C#のコンパイラーエラー解説でも、interface実装に関するエラーは、必要なメンバーの不足やシグネチャ不一致などが原因になることが説明されています。
8-2. アクセス修飾子で起きるエラー
interfaceのメンバーを実装するとき、通常はpublicを付ける必要があります。
エラーになりやすい例です。
C#public interface IAnimal
{
void Speak();
}
public class Dog : IAnimal
{
void Speak()
{
Console.WriteLine("ワンワン");
}
}
Speakメソッドにpublicが付いていないため、interfaceの実装として認識されません。
正しくは次のようにします。
C#public class Dog : IAnimal
{
public void Speak()
{
Console.WriteLine("ワンワン");
}
}
ただし、明示的インターフェイス実装ではpublicを付けません。
C#public class Dog : IAnimal
{
void IAnimal.Speak()
{
Console.WriteLine("ワンワン");
}
}
この違いは初心者が混乱しやすいポイントです。
8-3. 戻り値や引数の型が一致しないエラー
interfaceを実装するときは、メソッド名だけでなく、戻り値や引数の型も一致している必要があります。
エラー例です。
C#public interface ICalculator
{
int Add(int x, int y);
}
public class Calculator : ICalculator
{
public double Add(int x, int y)
{
return x + y;
}
}
interfaceでは戻り値がintなのに、実装クラスではdoubleになっています。
正しくは次のようにします。
C#public class Calculator : ICalculator
{
public int Add(int x, int y)
{
return x + y;
}
}
引数の型や数も一致している必要があります。
C#public interface IMessageSender
{
void Send(string message);
}
正しい実装です。
C#public class EmailSender : IMessageSender
{
public void Send(string message)
{
Console.WriteLine(message);
}
}
次のように引数が違うと、interfaceの実装として扱われません。
C#public class EmailSender : IMessageSender
{
public void Send(int message)
{
Console.WriteLine(message);
}
}
8-4. interfaceをnewしようとして起きるエラー
interface自体はインスタンス化できません。
エラー例です。
C#ILogger logger = new ILogger();
interfaceは契約であり、具体的な処理を持つオブジェクトではないためです。
正しくは、実装クラスをnewします。
C#ILogger logger = new ConsoleLogger();
または、DIコンテナなどから取得します。
C#public class OrderService
{
private readonly ILogger _logger;
public OrderService(ILogger logger)
{
_logger = logger;
}
}
interfaceは「変数の型」「引数の型」「戻り値の型」として使うことが多く、newするのは実装クラスです。
8-5. 複数interfaceで同名メンバーがある場合の対処法
複数のinterfaceに同じ名前のメンバーがある場合、通常の実装では1つのメソッドで両方を満たせることがあります。
C#public interface IA
{
void Execute();
}
public interface IB
{
void Execute();
}
public class Sample : IA, IB
{
public void Execute()
{
Console.WriteLine("実行します");
}
}
この場合、IA.ExecuteとIB.Executeは同じ実装を使います。
しかし、interfaceごとに別の処理をしたい場合は、明示的インターフェイス実装を使います。
C#public class Sample : IA, IB
{
void IA.Execute()
{
Console.WriteLine("IAとして実行");
}
void IB.Execute()
{
Console.WriteLine("IBとして実行");
}
}
呼び出すときは、interface型に変換します。
C#var sample = new Sample();
((IA)sample).Execute();
((IB)sample).Execute();
実行結果は次のようになります。
IAとして実行
IBとして実行
同名メンバーで処理を分けたいときは、明示的インターフェイス実装を検討しましょう。
9. C# interfaceの設計で押さえたいベストプラクティス
interfaceは便利ですが、何でもinterfaceにすればよいわけではありません。
設計を誤ると、逆にコードが複雑になります。
ここでは、C# interfaceを設計するときのベストプラクティスを紹介します。
9-1. interfaceは小さく分ける
interfaceは、できるだけ小さく分けるのが基本です。
悪い例です。
C#public interface IMachine
{
void Print();
void Scan();
void Fax();
}
このinterfaceを実装すると、印刷しかできない機械でもScanやFaxを実装しなければなりません。
よい例です。
C#public interface IPrintable
{
void Print();
}
public interface IScannable
{
void Scan();
}
public interface IFaxable
{
void Fax();
}
必要な機能だけを実装できます。
C#public class Printer : IPrintable
{
public void Print()
{
Console.WriteLine("印刷します");
}
}
public class MultiFunctionPrinter : IPrintable, IScannable, IFaxable
{
public void Print()
{
Console.WriteLine("印刷します");
}
public void Scan()
{
Console.WriteLine("スキャンします");
}
public void Fax()
{
Console.WriteLine("FAXします");
}
}
小さなinterfaceに分けることで、不要なメソッドを実装しなくて済みます。
9-2. 役割があいまいなinterfaceを作らない
役割があいまいなinterfaceは避けましょう。
悪い例です。
C#public interface IManager
{
void Execute();
}
IManagerという名前だけでは、何を管理するのか、何を実行するのかがわかりません。
よい例です。
C#public interface IUserRepository
{
User FindById(int id);
void Save(User user);
}
この名前なら、「ユーザー情報を保存・取得するもの」だとわかります。
interface名は、役割が明確に伝わる名前にしましょう。
| 悪い名前 | 改善例 |
|---|---|
IManager | IUserManager, IOrderManager |
IService | IEmailService, IPaymentService |
IProcessor | IImageProcessor, IOrderProcessor |
IData | IUserRepository, IProductRepository |
9-3. 実装クラスに依存しすぎない
interfaceは、実装クラスに依存しすぎないように設計することが大切です。
悪い例です。
C#public interface IFileLogger
{
void WriteToTextFile(string filePath, string message);
}
このinterfaceは、ファイル出力に限定されすぎています。
もしデータベースやクラウドにログを出したくなった場合、使いにくくなります。
より汎用的にするなら、次のようにします。
C#public interface ILogger
{
void Log(string message);
}
このinterfaceなら、実装方法を問いません。
C#public class FileLogger : ILogger
{
public void Log(string message)
{
File.WriteAllText("log.txt", message);
}
}
public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine(message);
}
}
interfaceは「何をするか」を表し、「どうやってするか」は実装クラスに任せるのが基本です。
9-4. 名前から役割が伝わるinterface名にする
interface名は、名前を見ただけで役割がわかるようにしましょう。
良い名前の例です。
C#public interface ILogger
{
void Log(string message);
}
public interface IEmailSender
{
void SendEmail(string to, string subject, string body);
}
public interface IOrderRepository
{
Order FindById(int id);
void Save(Order order);
}
名前から、何をするinterfaceなのかがわかります。
また、C#ではinterface名の先頭にIを付けるのが一般的です。
| 良いinterface名 | 意味 |
|---|---|
ILogger | ログを出力する |
IEmailSender | メールを送信する |
IOrderRepository | 注文データを扱う |
IPaymentService | 支払い処理を行う |
IClock | 現在時刻を取得する |
特に実務では、interface名がわかりにくいと、コード全体の理解が難しくなります。
9-5. interfaceを作りすぎないための判断基準
interfaceは便利ですが、作りすぎるとコードが複雑になります。
たとえば、実装クラスが1つしかなく、今後差し替える予定もなく、テストでもモック化しない場合、interfaceを作る必要性は低いかもしれません。
interfaceを作るか迷ったら、次の基準で考えましょう。
| 判断ポイント | interfaceを作るべきか |
|---|---|
| 複数の実装がある | 作る価値が高い |
| 将来差し替える可能性がある | 作る価値がある |
| 単体テストでモック化したい | 作る価値が高い |
| DIで注入したい | 作る価値がある |
| 実装が1つだけで変更予定もない | 無理に作らなくてもよい |
| 単なるデータクラス | interfaceは不要なことが多い |
interfaceは、設計を柔軟にするための道具です。
何でもinterfaceにするのではなく、「差し替えたいか」「抽象化したいか」「テストしやすくしたいか」を考えて使いましょう。
10. C# interfaceに関するよくある質問
最後に、C# interfaceについて初心者が疑問に感じやすいポイントをQ&A形式で整理します。
10-1. interfaceとクラスの違いは何ですか?
クラスは、具体的なデータや処理を持つ設計図です。
C#public class Dog
{
public string Name { get; set; }
public void Speak()
{
Console.WriteLine("ワンワン");
}
}
一方、interfaceは「どのようなメンバーを持つべきか」を決める契約です。
C#public interface IAnimal
{
void Speak();
}
クラスはインスタンス化できます。
C#Dog dog = new Dog();
interface自体はインスタンス化できません。
C#IAnimal animal = new IAnimal(); // エラー
ただし、interface型の変数に実装クラスを代入できます。
C#IAnimal animal = new Dog();
10-2. interfaceと継承の違いは何ですか?
継承は、「あるクラスをもとにして新しいクラスを作る」仕組みです。
C#public class Dog : Animal
{
}
この場合、DogはAnimalの一種です。
interfaceの実装は、「この機能を持っている」ことを表します。
C#public class Dog : IRunnable
{
public void Run()
{
Console.WriteLine("走ります");
}
}
この場合、DogはIRunnableという能力を持っています。
簡単にいうと、次のような違いです。
| 種類 | 意味 |
|---|---|
| 継承 | AはBの一種である |
| interface実装 | AはBという機能を持つ |
10-3. interfaceに処理を書くことはできますか?
現在のC#では、interfaceに既定実装を書くことができます。default interface membersにより、interfaceメンバーに実装を持たせられます。
C#public interface ILogger
{
void Log(string message);
void LogWarning(string message)
{
Log($"WARNING: {message}");
}
}
ただし、初心者のうちは、interfaceには基本的に「何を持つべきか」を定義し、具体的な処理は実装クラスに書くと考えるのがおすすめです。
C#public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine(message);
}
}
interfaceに処理を書けるからといって、何でもinterfaceに実装を書くと設計がわかりにくくなることがあります。
共通処理をしっかり持たせたい場合は、抽象クラスの利用も検討しましょう。
10-4. interfaceはいつ使うべきですか?
interfaceは、次のようなときに使うと効果的です。
| 使うべき場面 | 例 |
|---|---|
| 複数の実装を切り替えたい | ログ出力、支払い処理 |
| テストで差し替えたい | DBアクセスをモック化 |
| DIを使いたい | サービスをコンストラクター注入 |
| 複数クラスを同じ型で扱いたい | IAnimalでDogとCatを扱う |
| 能力を表したい | IPrintable, ISavable |
逆に、実装が1つしかなく、差し替えの予定もなく、テストでも使わない場合は、無理にinterfaceを作る必要はありません。
interfaceは「柔軟にしたい部分」に使うのが基本です。
10-5. 抽象クラスとinterfaceはどちらを先に覚えるべきですか?
初心者の場合は、まずinterfaceの基本的な使い方を覚えるのがおすすめです。
理由は、C#の実務では、interfaceがDI、単体テスト、リポジトリパターン、サービス設計などで頻繁に使われるからです。
ただし、抽象クラスも重要です。
学習順としては、次の流れがおすすめです。
| 順番 | 学ぶ内容 |
|---|---|
| 1 | クラス |
| 2 | 継承 |
| 3 | interface |
| 4 | 抽象クラス |
| 5 | DI・テスト・設計原則 |
interfaceは「できること」、抽象クラスは「共通の土台」と覚えると、使い分けがしやすくなります。
10-6. JavaのinterfaceとC#のinterfaceは同じですか?
JavaにもC#にもinterfaceがありますが、細かな仕様は異なります。
どちらも「クラスが実装すべき契約を定義する」という基本的な考え方は似ています。
ただし、C#にはC#独自の仕様があります。
たとえば、C#のinterfaceでは、プロパティ、イベント、インデクサーなどを定義できます。
C#public interface IUser
{
int Id { get; }
string Name { get; set; }
}
また、C#の新しいバージョンでは、interfaceの既定実装や静的メンバーなど、従来より多くの表現が可能になっています。
そのため、Java経験者であっても、C# interfaceの書き方や慣習はC#として学び直すのがおすすめです。
まとめ
C#のinterfaceは、クラスや構造体が実装すべき機能を定義するための仕組みです。
interfaceを使うと、「このクラスはこのメソッドを持つ」「このプロパティを持つ」といった契約をコードで表現できます。
基本構文は次のように書きます。
C#public interface ILogger
{
void Log(string message);
}
実装クラスでは、interfaceに定義されたメンバーを実装します。
C#public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine(message);
}
}
interfaceを使う主なメリットは次のとおりです。
| メリット | 内容 |
|---|---|
| 依存を減らせる | 具体的なクラスではなく抽象に依存できる |
| 実装を差し替えやすい | ConsoleLoggerからFileLoggerに変更しやすい |
| テストしやすい | モックやフェイクを使いやすい |
| 複数クラスを同じ型で扱える | DogもCatもIAnimalとして扱える |
| 複数の能力を表現できる | IPrintableとISavableを同時に実装できる |
| 保守性が上がる | 変更に強い設計にしやすい |
また、抽象クラスとの違いも重要です。
| 使うもの | 向いている場面 |
|---|---|
| interface | 「できること」「能力」「契約」を表したい |
| 抽象クラス | 「共通の土台」「共通処理」「共通状態」を持たせたい |
初心者のうちは、まず次のように覚えるとよいでしょう。
interfaceは「できること」を表す。
抽象クラスは「共通の土台」を表す。
C# interfaceを理解すると、DI、単体テスト、ポリモーフィズム、疎結合、設計原則といった実務で重要な考え方につながります。
最初は難しく感じるかもしれませんが、IAnimal、ILogger、IRepositoryのような小さな例から書いていくと、interfaceの便利さが少しずつ見えてきます。

