C#インターフェースとは?初心者がつまずく使い方・メリット・抽象クラスとの違いをサンプルコードで解説
はじめに
C#を学び始めると、クラス、メソッド、継承に続いて「インターフェース」という言葉が出てきます。
しかし、初心者にとってインターフェースは少しつまずきやすいポイントです。
「クラスと何が違うの?」
「なぜ中身の処理を書かないの?」
「抽象クラスとどう使い分けるの?」
「結局、どんな場面で便利なの?」
このように感じる人は多いです。
C#のインターフェースは、簡単に言うとクラスが実装すべき機能の形を決める仕組みです。処理の中身そのものではなく、「このメソッドを持っていること」「このプロパティを持っていること」といったルールを定義します。
インターフェースを理解すると、実装を差し替えやすいコード、テストしやすいコード、変更に強い設計を書きやすくなります。また、DI、ポリモーフィズム、抽象化といったC#開発でよく使われる考え方の理解にもつながります。
この記事では、C#インターフェースの基本から、使い方、メリット、サンプルコード、抽象クラスとの違い、初心者がつまずきやすいポイントまでわかりやすく解説します。
1. C#インターフェースとは?まずは「何をするものか」を初心者向けに解説
C#のインターフェースは、クラスに対して「この機能を持ってください」と約束させるための仕組みです。
たとえば、ログを出力する処理を考えてみましょう。
ログの出力先には、コンソール、ファイル、データベース、外部サービスなどさまざまな種類があります。しかし、使う側から見ると「ログを出力する」という操作は共通しています。
そこで、次のようなインターフェースを定義できます。
C#public interface ILogger
{
void Log(string message);
}
このILoggerは、「Logというメソッドを持つこと」を表しています。
実際にコンソールへ出力するか、ファイルへ出力するかは、インターフェースではなく実装クラスが決めます。
C#public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine(message);
}
}
このように、インターフェースは「何ができるか」を定義し、クラスは「どのように実行するか」を実装します。
1-1. インターフェースは「クラスが守るべきルール・契約」
インターフェースはよく「契約」と説明されます。
契約とは、「このインターフェースを実装するクラスは、必ずこのメンバーを用意してください」という約束です。
たとえば、次のインターフェースを考えます。
C#public interface IPrintable
{
void Print();
}
このIPrintableを実装するクラスは、必ずPrintメソッドを実装しなければなりません。
C#public class Report : IPrintable
{
public void Print()
{
Console.WriteLine("レポートを印刷します");
}
}
もしPrintメソッドを実装しないと、コンパイルエラーになります。
C#public class Report : IPrintable
{
// Printメソッドがないためエラー
}
つまり、インターフェースを実装するということは、「私はこの機能を必ず持っています」と宣言することです。
1-2. C#でインターフェースを使うと何ができるのか
C#でインターフェースを使うと、主に次のようなことができます。
まず、異なるクラスを同じ型として扱えます。
C#public interface IAnimal
{
void Speak();
}
public class Dog : IAnimal
{
public void Speak()
{
Console.WriteLine("ワン");
}
}
public class Cat : IAnimal
{
public void Speak()
{
Console.WriteLine("ニャー");
}
}
DogとCatは別々のクラスですが、どちらもIAnimalを実装しているため、IAnimal型として扱えます。
C#IAnimal animal1 = new Dog();
IAnimal animal2 = new Cat();
animal1.Speak();
animal2.Speak();
実行結果は次のようになります。
C#ワン
ニャー
このように、呼び出す側はDogなのかCatなのかを強く意識せず、IAnimalとして共通の操作を呼び出せます。
これがインターフェースの大きなメリットです。
1-3. クラス・メソッド・実装との関係をわかりやすく整理
インターフェース、クラス、メソッド、実装の関係は次のように整理できます。
| 用語 | 意味 |
|---|---|
| インターフェース | クラスが持つべき機能の形を決める |
| クラス | 実際の処理内容を持つ設計図 |
| メソッド | 処理をまとめたもの |
| 実装 | インターフェースで決めた機能の中身を書くこと |
たとえば、次のインターフェースがあります。
C#public interface IPayment
{
void Pay(int amount);
}
これは「支払い処理にはPayメソッドが必要です」というルールです。
クレジットカード決済クラスでは、次のように実装できます。
C#public class CreditCardPayment : IPayment
{
public void Pay(int amount)
{
Console.WriteLine($"{amount}円をクレジットカードで支払いました");
}
}
QRコード決済クラスでは、別の処理内容で実装できます。
C#public class QrPayment : IPayment
{
public void Pay(int amount)
{
Console.WriteLine($"{amount}円をQRコード決済で支払いました");
}
}
どちらもIPaymentという同じルールに従っていますが、実際の処理内容はクラスごとに異なります。
1-4. 「インターフェース」と「インターフェイス」は同じ意味?
「インターフェース」と「インターフェイス」は、基本的に同じ意味で使われます。
英語ではinterfaceです。日本語では表記ゆれがあり、「インターフェース」と書かれることもあれば、「インターフェイス」と書かれることもあります。
C#の文脈では、どちらも同じものを指していると考えて問題ありません。
ただし、技術記事や書籍では「インターフェース」と表記されることが多いです。この記事でも「インターフェース」という表記で統一します。
2. C#インターフェースの基本構文と使い方
ここからは、C#インターフェースの基本的な書き方を見ていきます。
インターフェースはinterfaceキーワードを使って定義します。
クラスに実装する場合は、クラス名の後ろに: インターフェース名の形で書きます。
2-1. interfaceキーワードでインターフェースを定義する
インターフェースの基本構文は次のとおりです。
C#public interface インターフェース名
{
戻り値の型 メソッド名(引数);
}
具体例を見てみましょう。
C#public interface IMessageSender
{
void Send(string message);
}
このIMessageSenderは、「文字列のメッセージを送信するSendメソッドを持つこと」を表しています。
インターフェース内では、基本的にメソッドの本体を書きません。
C#void Send(string message);
末尾が;になっている点に注目してください。
通常のメソッドでは次のように処理の中身を書きます。
C#public void Send(string message)
{
Console.WriteLine(message);
}
しかし、インターフェースでは「こういうメソッドが必要」という形だけを決めます。
2-2. クラスにインターフェースを実装する書き方
インターフェースをクラスに実装するには、クラス名の後ろに: インターフェース名と書きます。
C#public class EmailSender : IMessageSender
{
public void Send(string message)
{
Console.WriteLine($"メール送信: {message}");
}
}
この例では、EmailSenderクラスがIMessageSenderインターフェースを実装しています。
インターフェースにSendメソッドが定義されているため、EmailSenderクラスでは必ずSendメソッドを実装する必要があります。
インスタンスを作成して呼び出すと、次のようになります。
C#EmailSender sender = new EmailSender();
sender.Send("こんにちは");
実行結果は次のとおりです。
C#メール送信: こんにちは
インターフェースを使うと、クラスがどのような機能を持っているのかを明確にできます。
2-3. インターフェースに定義できるメンバー
C#のインターフェースには、主に次のようなメンバーを定義できます。
| メンバー | 例 |
|---|---|
| メソッド | void Save(); |
| プロパティ | string Name { get; set; } |
| イベント | event EventHandler Completed; |
| インデクサー | string this[int index] { get; } |
初心者のうちは、まずメソッドとプロパティを理解すれば十分です。
メソッドの例です。
C#public interface IWorker
{
void Work();
}
プロパティの例です。
C#public interface IUser
{
string Name { get; set; }
}
メソッドとプロパティを組み合わせることもできます。
C#public interface IProduct
{
string Name { get; set; }
int Price { get; set; }
void Display();
}
この場合、実装クラスではName、Price、Displayをすべて実装する必要があります。
2-4. メソッド・プロパティを使った基本サンプルコード
次に、メソッドとプロパティを持つインターフェースのサンプルを見てみましょう。
C#public interface IUser
{
string Name { get; set; }
void ShowProfile();
}
このインターフェースを実装するクラスを作成します。
C#public class Customer : IUser
{
public string Name { get; set; }
public Customer(string name)
{
Name = name;
}
public void ShowProfile()
{
Console.WriteLine($"ユーザー名: {Name}");
}
}
使い方は次のとおりです。
C#Customer customer = new Customer("田中");
customer.ShowProfile();
実行結果です。
C#ユーザー名: 田中
この例では、IUserインターフェースが「NameプロパティとShowProfileメソッドを持つこと」を決めています。
Customerクラスは、そのルールに従って実装しています。
2-5. インターフェース型の変数でオブジェクトを扱う方法
インターフェースの便利な点は、実装クラスのインスタンスをインターフェース型の変数に代入できることです。
C#IUser user = new Customer("佐藤");
user.ShowProfile();
このコードでは、変数の型はIUserですが、実際に入っているオブジェクトはCustomerです。
C#IUser user = new Customer("佐藤");
これは初心者がつまずきやすい部分ですが、次のように考えるとわかりやすいです。
CustomerはIUserを実装しているため、「CustomerはIUserとして扱える」ということです。
この考え方により、呼び出し側は具体的なクラスに依存せず、インターフェースを通じて処理を呼び出せます。
C#public void PrintUser(IUser user)
{
user.ShowProfile();
}
このメソッドは、IUserを実装しているクラスであれば何でも受け取れます。
C#PrintUser(new Customer("鈴木"));
将来的にAdminUserクラスを作ったとしても、IUserを実装していれば同じメソッドで扱えます。
C#public class AdminUser : IUser
{
public string Name { get; set; }
public AdminUser(string name)
{
Name = name;
}
public void ShowProfile()
{
Console.WriteLine($"管理者: {Name}");
}
}
C#PrintUser(new AdminUser("管理者A"));
このように、インターフェース型を使うことで、柔軟なコードを書けます。
3. C#インターフェースのメリット
C#インターフェースのメリットは、単に「ルールを作れる」だけではありません。
実務では、変更に強い設計、テストしやすい設計、実装を差し替えやすい設計を作るために使われます。
ここでは、代表的なメリットを順番に解説します。
3-1. 実装クラスを差し替えやすくなる
インターフェースを使うと、処理の呼び出し側を変更せずに、実装クラスを差し替えやすくなります。
たとえば、メッセージ送信処理を考えます。
C#public interface IMessageSender
{
void Send(string message);
}
メール送信用のクラスです。
C#public class EmailSender : IMessageSender
{
public void Send(string message)
{
Console.WriteLine($"メール送信: {message}");
}
}
SMS送信用のクラスです。
C#public class SmsSender : IMessageSender
{
public void Send(string message)
{
Console.WriteLine($"SMS送信: {message}");
}
}
呼び出し側は、IMessageSenderに依存させます。
C#public class NotificationService
{
private readonly IMessageSender _sender;
public NotificationService(IMessageSender sender)
{
_sender = sender;
}
public void Notify(string message)
{
_sender.Send(message);
}
}
使う側で実装クラスを差し替えられます。
C#var service1 = new NotificationService(new EmailSender());
service1.Notify("お知らせがあります");
var service2 = new NotificationService(new SmsSender());
service2.Notify("認証コードを送信しました");
NotificationServiceは、メールなのかSMSなのかを知る必要がありません。
「メッセージを送信できるもの」であればよいのです。
3-2. クラス同士の依存を減らして変更に強いコードになる
インターフェースを使わない場合、クラスが具体的なクラスに直接依存しやすくなります。
C#public class NotificationService
{
private readonly EmailSender _sender = new EmailSender();
public void Notify(string message)
{
_sender.Send(message);
}
}
このコードでは、NotificationServiceがEmailSenderに強く依存しています。
後からSMS送信に変えたい場合、NotificationServiceの中身を修正する必要があります。
一方、インターフェースを使えば、NotificationServiceはIMessageSenderにだけ依存します。
C#public class NotificationService
{
private readonly IMessageSender _sender;
public NotificationService(IMessageSender sender)
{
_sender = sender;
}
public void Notify(string message)
{
_sender.Send(message);
}
}
この設計では、NotificationServiceは具体的な送信方法を知りません。
メール送信、SMS送信、チャット送信など、どの実装でもIMessageSenderを実装していれば使えます。
このように、インターフェースを使うとクラス同士の結びつきが弱くなり、変更に強いコードになります。
3-3. 複数のクラスに共通の使い方を強制できる
インターフェースを使うと、複数のクラスに同じメソッドやプロパティを持たせることができます。
たとえば、図形を表すクラスを考えます。
C#public interface IShape
{
double GetArea();
}
四角形クラスです。
C#public class Rectangle : IShape
{
public double Width { get; }
public double Height { get; }
public Rectangle(double width, double height)
{
Width = width;
Height = height;
}
public double GetArea()
{
return Width * Height;
}
}
円クラスです。
C#public class Circle : IShape
{
public double Radius { get; }
public Circle(double radius)
{
Radius = radius;
}
public double GetArea()
{
return Radius * Radius * Math.PI;
}
}
どちらのクラスもIShapeを実装しているため、必ずGetAreaメソッドを持ちます。
C#List<IShape> shapes = new List<IShape>
{
new Rectangle(10, 5),
new Circle(3)
};
foreach (IShape shape in shapes)
{
Console.WriteLine(shape.GetArea());
}
このように、異なるクラスに共通の使い方を強制できる点もインターフェースのメリットです。
3-4. テストしやすいコードを書ける
インターフェースは、単体テストでもよく使われます。
たとえば、注文処理の中でメール送信を行うクラスを考えます。
C#public interface IEmailSender
{
void Send(string to, string subject, string body);
}
本番用のメール送信クラスです。
C#public class EmailSender : IEmailSender
{
public void Send(string to, string subject, string body)
{
Console.WriteLine($"メールを送信しました: {to}");
}
}
注文処理クラスです。
C#public class OrderService
{
private readonly IEmailSender _emailSender;
public OrderService(IEmailSender emailSender)
{
_emailSender = emailSender;
}
public void CompleteOrder(string email)
{
// 注文完了処理
_emailSender.Send(email, "注文完了", "ご注文ありがとうございました。");
}
}
テストでは、本物のメール送信ではなく、テスト用のクラスに差し替えられます。
C#public class FakeEmailSender : IEmailSender
{
public bool WasSent { get; private set; }
public void Send(string to, string subject, string body)
{
WasSent = true;
}
}
テストコードのイメージです。
C#var fakeEmailSender = new FakeEmailSender();
var orderService = new OrderService(fakeEmailSender);
orderService.CompleteOrder("test@example.com");
Console.WriteLine(fakeEmailSender.WasSent);
このように、インターフェースを使うことで、本番用の処理をテスト用の処理に差し替えやすくなります。
3-5. DI・ポリモーフィズムを理解する土台になる
C#の実務では、DIという考え方がよく使われます。
DIは「Dependency Injection」の略で、日本語では「依存性の注入」と呼ばれます。
簡単に言うと、クラスの中で必要なオブジェクトを直接作るのではなく、外から渡す設計のことです。
C#public class UserService
{
private readonly IUserRepository _repository;
public UserService(IUserRepository repository)
{
_repository = repository;
}
}
このようなコードでは、UserServiceはIUserRepositoryというインターフェースに依存しています。
実際にデータベースを使うのか、テスト用のメモリデータを使うのかは、外側から渡す実装によって決まります。
また、インターフェースはポリモーフィズムの理解にもつながります。
ポリモーフィズムとは、同じ呼び出し方でも、実際のオブジェクトによって異なる処理が実行される性質です。
C#IAnimal animal = new Dog();
animal.Speak();
animal = new Cat();
animal.Speak();
同じSpeakメソッドを呼んでいますが、Dogなら「ワン」、Catなら「ニャー」と異なる処理が実行されます。
インターフェースは、C#で柔軟な設計を学ぶための重要な土台です。
4. サンプルコードで理解するC#インターフェースの実践例
ここからは、より実務に近い例でC#インターフェースの使い方を見ていきます。
インターフェースは、単なる文法として覚えるよりも、「どんな問題を解決するために使うのか」をサンプルコードで理解することが大切です。
4-1. ログ出力処理をインターフェース化する例
ログ出力は、インターフェースの例として非常にわかりやすい題材です。
まず、ログ出力用のインターフェースを定義します。
C#public interface ILogger
{
void Log(string message);
}
コンソールにログを出すクラスです。
C#public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine($"[Console] {message}");
}
}
ファイルにログを出すクラスです。
C#public class FileLogger : ILogger
{
public void Log(string message)
{
File.AppendAllText("log.txt", $"[File] {message}{Environment.NewLine}");
}
}
ログを使うサービスクラスです。
C#public class AppService
{
private readonly ILogger _logger;
public AppService(ILogger logger)
{
_logger = logger;
}
public void Execute()
{
_logger.Log("処理を開始しました");
// 何らかの処理
Console.WriteLine("メイン処理を実行中...");
_logger.Log("処理を終了しました");
}
}
実行例です。
C#ILogger logger = new ConsoleLogger();
var service = new AppService(logger);
service.Execute();
この設計では、AppServiceはログの出力先を知りません。
コンソールに出すか、ファイルに出すかは、渡すILoggerの実装によって決まります。
4-2. ファイル保存とデータベース保存を切り替える例
次に、データ保存処理をインターフェース化する例を見てみましょう。
C#public interface IDataStore
{
void Save(string data);
}
ファイルに保存するクラスです。
C#public class FileDataStore : IDataStore
{
public void Save(string data)
{
File.WriteAllText("data.txt", data);
Console.WriteLine("ファイルに保存しました");
}
}
データベースに保存するクラスです。
C#public class DatabaseDataStore : IDataStore
{
public void Save(string data)
{
Console.WriteLine($"データベースに保存しました: {data}");
}
}
保存処理を使うサービスです。
C#public class DataService
{
private readonly IDataStore _dataStore;
public DataService(IDataStore dataStore)
{
_dataStore = dataStore;
}
public void Register(string data)
{
_dataStore.Save(data);
}
}
ファイル保存を使う場合です。
C#var service = new DataService(new FileDataStore());
service.Register("ユーザーデータ");
データベース保存に切り替える場合です。
C#var service = new DataService(new DatabaseDataStore());
service.Register("ユーザーデータ");
DataServiceのコードは一切変更していません。
変更しているのは、外から渡す実装クラスだけです。
これがインターフェースを使う大きなメリットです。
4-3. 複数クラスで同じインターフェースを実装する例
インターフェースは、複数のクラスに共通の機能を持たせたいときにも使えます。
たとえば、通知処理を考えます。
C#public interface INotification
{
void Send(string message);
}
メール通知です。
C#public class EmailNotification : INotification
{
public void Send(string message)
{
Console.WriteLine($"メール通知: {message}");
}
}
SMS通知です。
C#public class SmsNotification : INotification
{
public void Send(string message)
{
Console.WriteLine($"SMS通知: {message}");
}
}
チャット通知です。
C#public class ChatNotification : INotification
{
public void Send(string message)
{
Console.WriteLine($"チャット通知: {message}");
}
}
これらはすべてINotificationとして扱えます。
C#List<INotification> notifications = new List<INotification>
{
new EmailNotification(),
new SmsNotification(),
new ChatNotification()
};
foreach (INotification notification in notifications)
{
notification.Send("システムメンテナンスのお知らせ");
}
実行結果です。
C#メール通知: システムメンテナンスのお知らせ
SMS通知: システムメンテナンスのお知らせ
チャット通知: システムメンテナンスのお知らせ
異なるクラスでも、同じインターフェースを実装していれば、同じように扱えます。
4-4. インターフェース型で呼び出すと何が便利なのか
インターフェース型で呼び出すメリットは、呼び出し側が具体的なクラスを知らなくてよい点です。
たとえば、次のようなメソッドを作れます。
C#public void SendNotification(INotification notification, string message)
{
notification.Send(message);
}
このメソッドは、INotificationを実装しているクラスであれば何でも受け取れます。
C#SendNotification(new EmailNotification(), "こんにちは");
SendNotification(new SmsNotification(), "こんにちは");
SendNotification(new ChatNotification(), "こんにちは");
もし引数の型をEmailNotificationにしていたら、メール通知しか受け取れません。
C#public void SendNotification(EmailNotification notification, string message)
{
notification.Send(message);
}
これでは、SMS通知やチャット通知に対応しにくくなります。
一方、インターフェース型にしておくと、将来的に新しい通知方法を追加しても、呼び出し側の変更を少なくできます。
C#public class PushNotification : INotification
{
public void Send(string message)
{
Console.WriteLine($"プッシュ通知: {message}");
}
}
新しいクラスを追加しても、既存のSendNotificationメソッドはそのまま使えます。
C#SendNotification(new PushNotification(), "新しい通知です");
このように、インターフェース型で扱うと、拡張しやすく変更に強いコードになります。
4-5. 実務でよく使われる命名規則「I〇〇」
C#では、インターフェース名の先頭にIを付ける命名規則がよく使われます。
たとえば、次のような名前です。
C#ILogger
IRepository
IUserService
IDataStore
INotification
IDisposable
IEnumerable
IComparable
このIはInterfaceの頭文字です。
C#では、インターフェースかクラスかを名前で判別しやすくするために、I〇〇という命名が一般的です。
たとえば、ログ出力のインターフェースなら次のように書きます。
C#public interface ILogger
{
void Log(string message);
}
実装クラスは、具体的な処理内容がわかる名前にします。
C#public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine(message);
}
}
C#public class FileLogger : ILogger
{
public void Log(string message)
{
File.AppendAllText("log.txt", message);
}
}
インターフェース名には「何ができるか」、実装クラス名には「どのように実現するか」を表す名前を付けるとわかりやすくなります。
5. 初心者がつまずきやすいC#インターフェースのポイント
C#インターフェースは便利ですが、初心者が混乱しやすい点もあります。
ここでは、特につまずきやすいポイントを整理します。
5-1. インターフェースだけではインスタンス化できない
インターフェースは、クラスのように直接インスタンス化できません。
次のコードはエラーになります。
C#ILogger logger = new ILogger();
インターフェースは「機能の形」を定義するだけで、実際の処理内容を持つ具体的なクラスではないからです。
正しくは、インターフェースを実装したクラスをインスタンス化します。
C#ILogger logger = new ConsoleLogger();
このコードでは、変数の型はILoggerですが、実際に作成しているのはConsoleLoggerです。
C#ConsoleLogger consoleLogger = new ConsoleLogger();
これも正しいですが、柔軟性を持たせたい場合はインターフェース型で受けることが多いです。
C#ILogger logger = new ConsoleLogger();
初心者のうちは、「インターフェースはnewできない。newするのは実装クラス」と覚えるとよいです。
5-2. 定義したメンバーは実装クラスで実装が必要
インターフェースに定義したメンバーは、実装クラスで必ず実装する必要があります。
C#public interface IPrinter
{
void Print();
}
このインターフェースを実装するクラスでは、Printメソッドが必要です。
C#public class ReportPrinter : IPrinter
{
public void Print()
{
Console.WriteLine("レポートを印刷します");
}
}
もし実装しないとエラーになります。
C#public class ReportPrinter : IPrinter
{
// Printメソッドがないためエラー
}
また、メソッド名、戻り値の型、引数の型が一致している必要があります。
C#public interface ICalculator
{
int Add(int x, int y);
}
正しい実装です。
C#public class Calculator : ICalculator
{
public int Add(int x, int y)
{
return x + y;
}
}
間違った実装です。
C#public class Calculator : ICalculator
{
public void Add(int x, int y)
{
Console.WriteLine(x + y);
}
}
戻り値の型がintではなくvoidになっているため、インターフェースの実装として認められません。
5-3. なぜ処理内容を書かないのかがわかりにくい
初心者がインターフェースで特に疑問に感じやすいのが、「なぜ処理内容を書かないのか」という点です。
インターフェースの目的は、処理内容を書くことではなく、使い方のルールを決めることです。
たとえば、次のインターフェースがあります。
C#public interface IPayment
{
void Pay(int amount);
}
これは「支払う」という操作があることを表しています。
しかし、支払い方法はさまざまです。
C#public class CreditCardPayment : IPayment
{
public void Pay(int amount)
{
Console.WriteLine($"{amount}円をクレジットカードで支払いました");
}
}
C#public class BankTransferPayment : IPayment
{
public void Pay(int amount)
{
Console.WriteLine($"{amount}円を銀行振込で支払いました");
}
}
C#public class CashPayment : IPayment
{
public void Pay(int amount)
{
Console.WriteLine($"{amount}円を現金で支払いました");
}
}
インターフェース側で処理内容を決めてしまうと、支払い方法ごとの差を表現しにくくなります。
そのため、インターフェースでは「Payメソッドがあること」だけを定義し、具体的な処理は各クラスに任せます。
5-4. インターフェース型と実装クラス型の違い
インターフェース型と実装クラス型の違いも、初心者が混乱しやすいポイントです。
次のコードを見てください。
C#ConsoleLogger logger1 = new ConsoleLogger();
ILogger logger2 = new ConsoleLogger();
どちらもConsoleLoggerのインスタンスを作成しています。
違いは、変数の型です。
logger1はConsoleLogger型です。
C#ConsoleLogger logger1 = new ConsoleLogger();
この場合、ConsoleLoggerクラスに定義されたメンバーを使えます。
一方、logger2はILogger型です。
C#ILogger logger2 = new ConsoleLogger();
この場合、変数を通じて使えるのは、ILoggerに定義されているメンバーです。
たとえば、次のようなクラスがあるとします。
C#public interface ILogger
{
void Log(string message);
}
public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine(message);
}
public void Clear()
{
Console.Clear();
}
}
ConsoleLogger型ならClearメソッドも呼び出せます。
C#ConsoleLogger logger1 = new ConsoleLogger();
logger1.Clear();
しかし、ILogger型ではILoggerに定義されていないClearは呼び出せません。
C#ILogger logger2 = new ConsoleLogger();
logger2.Log("ログ");
// logger2.Clear(); // エラー
インターフェース型で扱うということは、「そのインターフェースで定義された機能だけを見る」ということです。
5-5. 明示的インターフェース実装とは何か
C#には、明示的インターフェース実装という書き方があります。
通常の実装は次のように書きます。
C#public interface IPrinter
{
void Print();
}
public class Report : IPrinter
{
public void Print()
{
Console.WriteLine("印刷します");
}
}
一方、明示的インターフェース実装では、次のように書きます。
C#public class Report : IPrinter
{
void IPrinter.Print()
{
Console.WriteLine("IPrinterとして印刷します");
}
}
この場合、Report型の変数から直接Printを呼び出せません。
C#Report report = new Report();
// report.Print(); // エラー
IPrinter型として扱うと呼び出せます。
C#IPrinter printer = new Report();
printer.Print();
明示的インターフェース実装は、主に次のような場面で使われます。
C#public interface IPrintable
{
void Execute();
}
public interface ISavable
{
void Execute();
}
同じ名前のメソッドを持つ複数のインターフェースを実装する場合、どちらのインターフェースのメソッドかを明確にできます。
C#public class Document : IPrintable, ISavable
{
void IPrintable.Execute()
{
Console.WriteLine("印刷します");
}
void ISavable.Execute()
{
Console.WriteLine("保存します");
}
}
呼び出し側です。
C#Document document = new Document();
IPrintable printable = document;
printable.Execute();
ISavable savable = document;
savable.Execute();
初心者の段階では頻繁に使う必要はありませんが、「インターフェース型に変換したときだけ呼び出せる実装方法」と理解しておくとよいです。
5-6. 使いすぎると逆にコードが複雑になるケース
インターフェースは便利ですが、何でもインターフェースにすればよいわけではありません。
たとえば、次のように実装クラスが1つしかなく、今後差し替える予定もない場合を考えます。
C#public interface ICustomerNameFormatter
{
string Format(string firstName, string lastName);
}
public class CustomerNameFormatter : ICustomerNameFormatter
{
public string Format(string firstName, string lastName)
{
return $"{lastName} {firstName}";
}
}
このような単純な処理に毎回インターフェースを作ると、ファイル数やクラス数が増え、かえって読みにくくなることがあります。
インターフェースが効果を発揮するのは、次のような場面です。
| インターフェースが有効な場面 | 例 |
|---|---|
| 実装を差し替えたい | ファイル保存とDB保存を切り替える |
| 複数の実装がある | メール通知、SMS通知、チャット通知 |
| テストでモックにしたい | 外部APIやメール送信を置き換える |
| 外部に公開する機能の形を決めたい | ライブラリやサービスの設計 |
逆に、次のような場合は無理に使わなくてもよいです。
| 無理に使わなくてもよい場面 | 理由 |
|---|---|
| 実装が1つだけ | 抽象化のメリットが小さい |
| 小さなサンプルコード | 複雑さが増える |
| 変更予定がない内部処理 | 直接クラスを使った方が簡潔 |
| 単なるデータ保持クラス | インターフェース化する意味が薄い |
インターフェースは、目的を持って使うことが大切です。
6. C#インターフェースと抽象クラスの違い
C#のインターフェースとよく比較されるのが、抽象クラスです。
どちらも「直接使うためのものではなく、他のクラスに実装や継承をさせるためのもの」という点では似ています。
しかし、役割や使いどころには違いがあります。
6-1. インターフェースと抽象クラスの役割の違い
インターフェースは、「何ができるか」を定義します。
C#public interface IFlyable
{
void Fly();
}
これは「飛ぶことができる」という能力を表しています。
鳥、飛行機、ドローンなど、まったく異なる種類のクラスでも、飛ぶことができるならIFlyableを実装できます。
C#public class Bird : IFlyable
{
public void Fly()
{
Console.WriteLine("鳥が飛びます");
}
}
public class Airplane : IFlyable
{
public void Fly()
{
Console.WriteLine("飛行機が飛びます");
}
}
一方、抽象クラスは、「共通する性質や処理を持つ親クラス」として使われます。
C#public abstract class Animal
{
public string Name { get; set; }
public void Eat()
{
Console.WriteLine("食べます");
}
public abstract void Speak();
}
Animalは、犬や猫などの共通の親として使えます。
C#public class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("ワン");
}
}
インターフェースは「能力・契約」、抽象クラスは「共通の土台」と考えるとわかりやすいです。
6-2. 実装を持てるかどうかの違い
従来のインターフェースは、基本的にメソッドの処理内容を持たず、形だけを定義するものでした。
C#public interface ILogger
{
void Log(string message);
}
一方、抽象クラスは共通の処理を持てます。
C#public abstract class LoggerBase
{
public void WriteHeader()
{
Console.WriteLine("=== ログ開始 ===");
}
public abstract void Log(string message);
}
継承先では、共通処理をそのまま使いつつ、必要な部分だけ実装できます。
C#public class ConsoleLogger : LoggerBase
{
public override void Log(string message)
{
Console.WriteLine(message);
}
}
ただし、C# 8.0以降では、インターフェースにもデフォルト実装を持たせることができます。
C#public interface ILogger
{
void Log(string message);
void LogError(string message)
{
Log($"ERROR: {message}");
}
}
とはいえ、初心者のうちは「インターフェースは基本的に形を決めるもの」「抽象クラスは共通処理を持てるもの」と理解しておくとよいです。
6-3. 複数実装できるかどうかの違い
C#では、クラスの継承は1つだけです。
C#public class Dog : Animal
{
}
複数のクラスを同時に継承することはできません。
C#// C#では不可
// public class Dog : Animal, Mammal
// {
// }
一方、インターフェースは複数実装できます。
C#public interface IRunnable
{
void Run();
}
public interface ISwimmable
{
void Swim();
}
public class Dog : IRunnable, ISwimmable
{
public void Run()
{
Console.WriteLine("走ります");
}
public void Swim()
{
Console.WriteLine("泳ぎます");
}
}
このように、1つのクラスに複数の能力を持たせたい場合、インターフェースが便利です。
6-4. フィールド・コンストラクタの扱いの違い
抽象クラスはフィールドやコンストラクタを持てます。
C#public abstract class Animal
{
protected string Name;
public Animal(string name)
{
Name = name;
}
public void ShowName()
{
Console.WriteLine(Name);
}
}
継承クラスでは、親クラスのコンストラクタを呼び出せます。
C#public class Dog : Animal
{
public Dog(string name) : base(name)
{
}
}
一方、インターフェースは通常のインスタンスフィールドやコンストラクタを持てません。
C#public interface IAnimal
{
string Name { get; set; }
void Speak();
}
インターフェースは、状態を保持するためのものではなく、外から見た機能の形を定義するためのものです。
状態や共通処理を持たせたい場合は、抽象クラスを検討します。
6-5. 比較表でわかるインターフェースと抽象クラスの違い
インターフェースと抽象クラスの違いを表で整理します。
| 比較項目 | インターフェース | 抽象クラス |
|---|---|---|
| 主な役割 | 機能の契約を定義する | 共通の土台を定義する |
| キーワード | interface | abstract class |
| 実装方法 | 実装する | 継承する |
| 複数指定 | 複数実装できる | クラス継承は1つだけ |
| メソッドの中身 | 基本は書かない | 共通処理を書ける |
| フィールド | 通常のインスタンスフィールドは持てない | 持てる |
| コンストラクタ | 持てない | 持てる |
| 使いどころ | 能力や仕様を表す | 共通処理や状態を共有する |
| 例 | ILogger, IDisposable | AnimalBase, RepositoryBase |
初心者向けに簡単にまとめると、次のようになります。
インターフェースは「できること」を表します。
C#public interface IPrintable
{
void Print();
}
抽象クラスは「共通の親」を表します。
C#public abstract class Document
{
public string Title { get; set; }
public abstract void Print();
}
6-6. どちらを使うべきか判断する基準
インターフェースを使うか、抽象クラスを使うか迷った場合は、次の基準で判断するとわかりやすいです。
インターフェースを使うとよい場面です。
| 判断基準 | 例 |
|---|---|
| 複数の実装を切り替えたい | ログ出力、通知、保存処理 |
| クラスの種類に関係なく能力を表したい | 印刷できる、保存できる、比較できる |
| テストでモックに差し替えたい | メール送信、外部API呼び出し |
| DIで依存を注入したい | サービス、リポジトリ |
抽象クラスを使うとよい場面です。
| 判断基準 | 例 |
|---|---|
| 共通の処理を持たせたい | 共通ログ処理、共通バリデーション |
| 共通の状態を持たせたい | 名前、ID、作成日時 |
| 同じ種類のクラスをまとめたい | Animalを継承するDogやCat |
| 基本処理を親クラスにまとめたい | RepositoryBaseなど |
たとえば、「メール送信とSMS送信を切り替えたい」ならインターフェースが向いています。
C#public interface IMessageSender
{
void Send(string message);
}
一方、「犬と猫に共通するNameやEatメソッドを持たせたい」なら抽象クラスが向いています。
C#public abstract class Animal
{
public string Name { get; set; }
public void Eat()
{
Console.WriteLine("食べます");
}
public abstract void Speak();
}
迷ったときは、「共通処理を持たせたいのか」「機能の形だけを決めたいのか」で考えると判断しやすくなります。
7. インターフェースを使うべき場面・使わなくてもよい場面
インターフェースは便利ですが、使うべき場面と使わなくてもよい場面があります。
重要なのは、「なぜインターフェースを使うのか」を意識することです。
7-1. 複数の実装を切り替えたいとき
インターフェースが特に役立つのは、複数の実装を切り替えたいときです。
たとえば、支払い方法を切り替えるケースです。
C#public interface IPaymentService
{
void Pay(int amount);
}
クレジットカード決済です。
C#public class CreditCardPaymentService : IPaymentService
{
public void Pay(int amount)
{
Console.WriteLine($"{amount}円をクレジットカードで支払いました");
}
}
銀行振込です。
C#public class BankTransferPaymentService : IPaymentService
{
public void Pay(int amount)
{
Console.WriteLine($"{amount}円を銀行振込で支払いました");
}
}
呼び出し側です。
C#public class PaymentController
{
private readonly IPaymentService _paymentService;
public PaymentController(IPaymentService paymentService)
{
_paymentService = paymentService;
}
public void Checkout(int amount)
{
_paymentService.Pay(amount);
}
}
このようにしておくと、支払い方法を追加・変更しても、呼び出し側への影響を小さくできます。
7-2. 外部から使う機能の形だけを決めたいとき
ライブラリやサービスを設計するとき、外部から使う機能の形だけを決めたい場合があります。
たとえば、プラグインのような仕組みを考えます。
C#public interface IPlugin
{
string Name { get; }
void Execute();
}
外部の開発者は、このインターフェースを実装すれば、独自のプラグインを作れます。
C#public class SamplePlugin : IPlugin
{
public string Name => "サンプルプラグイン";
public void Execute()
{
Console.WriteLine("プラグインを実行しました");
}
}
アプリケーション側は、IPluginとして扱えばよいです。
C#public class PluginRunner
{
public void Run(IPlugin plugin)
{
Console.WriteLine($"{plugin.Name}を開始します");
plugin.Execute();
}
}
インターフェースを公開しておくことで、「この形に合わせて実装してください」というルールを示せます。
7-3. 単体テストでモックに差し替えたいとき
外部API、データベース、メール送信、ファイル操作などは、単体テストでそのまま実行したくないことがあります。
このような処理は、インターフェースにしておくとテストしやすくなります。
C#public interface IUserRepository
{
User FindById(int id);
}
本番用の実装です。
C#public class DatabaseUserRepository : IUserRepository
{
public User FindById(int id)
{
Console.WriteLine("データベースからユーザーを取得します");
return new User { Id = id, Name = "本番ユーザー" };
}
}
テスト用の実装です。
C#public class FakeUserRepository : IUserRepository
{
public User FindById(int id)
{
return new User { Id = id, Name = "テストユーザー" };
}
}
サービスクラスです。
C#public class UserService
{
private readonly IUserRepository _repository;
public UserService(IUserRepository repository)
{
_repository = repository;
}
public string GetUserName(int id)
{
User user = _repository.FindById(id);
return user.Name;
}
}
テスト時には、FakeUserRepositoryを渡せます。
C#var service = new UserService(new FakeUserRepository());
string name = service.GetUserName(1);
Console.WriteLine(name);
本物のデータベースに接続しなくても、UserServiceの処理を確認できます。
7-4. 共通処理を持たせたいだけなら抽象クラスを検討する
複数のクラスに同じ処理を持たせたいだけなら、インターフェースではなく抽象クラスを検討することもあります。
たとえば、ログの前後に共通の処理を入れたい場合です。
C#public abstract class ProcessorBase
{
public void Execute()
{
Console.WriteLine("処理開始");
Process();
Console.WriteLine("処理終了");
}
protected abstract void Process();
}
具体的な処理は継承先で実装します。
C#public class ImportProcessor : ProcessorBase
{
protected override void Process()
{
Console.WriteLine("インポート処理を実行します");
}
}
使い方です。
C#var processor = new ImportProcessor();
processor.Execute();
このように、「共通処理を親クラスに置きたい」という目的なら、抽象クラスの方が自然な場合があります。
インターフェースは「共通処理を共有するため」というより、「共通の使い方を決めるため」に使うものです。
7-5. 小規模コードで無理にインターフェースを使わなくてよいケース
小規模なコードや学習用のコードでは、無理にインターフェースを使わなくてもよい場合があります。
たとえば、次のような単純な計算クラスです。
C#public class TaxCalculator
{
public int Calculate(int price)
{
return (int)(price * 1.1);
}
}
このクラスに対して、すぐにインターフェースを作る必要はないかもしれません。
C#public interface ITaxCalculator
{
int Calculate(int price);
}
実装が1つしかなく、差し替えの予定もなく、テストでも困らないなら、インターフェース化しない方がシンプルです。
インターフェースは、コードを柔軟にする一方で、ファイル数や抽象度を増やします。
そのため、次のような視点で判断するとよいです。
| 判断ポイント | インターフェースを使うべきか |
|---|---|
| 実装が複数ある | 使う価値が高い |
| 将来差し替えたい | 使う価値が高い |
| テストでモックにしたい | 使う価値が高い |
| 実装が1つだけで変更予定がない | 無理に使わなくてもよい |
| 学習用の小さなコード | まずはクラスだけでもよい |
インターフェースは「使えば必ず良い設計になる」ものではありません。
必要な場面で適切に使うことが大切です。
8. C#インターフェースの応用知識
基本的な使い方に慣れてきたら、C#インターフェースの応用的な機能も理解しておくと役立ちます。
ここでは、複数インターフェースの実装、インターフェースの継承、ジェネリックとの組み合わせ、標準ライブラリの代表例、デフォルトインターフェースメソッドについて解説します。
8-1. 複数のインターフェースを1つのクラスで実装する
C#では、1つのクラスが複数のインターフェースを実装できます。
C#public interface IReadable
{
void Read();
}
public interface IWritable
{
void Write(string text);
}
両方を実装するクラスです。
C#public class FileDocument : IReadable, IWritable
{
public void Read()
{
Console.WriteLine("ファイルを読み込みます");
}
public void Write(string text)
{
Console.WriteLine($"ファイルに書き込みます: {text}");
}
}
使い方です。
C#FileDocument document = new FileDocument();
document.Read();
document.Write("Hello");
インターフェース型として分けて扱うこともできます。
C#IReadable readable = document;
readable.Read();
IWritable writable = document;
writable.Write("こんにちは");
このように、1つのクラスに複数の役割を持たせたい場合に便利です。
8-2. インターフェースの継承
インターフェースは、別のインターフェースを継承できます。
C#public interface IEntity
{
int Id { get; set; }
}
IEntityを継承したインターフェースです。
C#public interface IUser : IEntity
{
string Name { get; set; }
}
IUserを実装するクラスは、IUserのメンバーだけでなく、IEntityのメンバーも実装する必要があります。
C#public class User : IUser
{
public int Id { get; set; }
public string Name { get; set; }
}
インターフェースの継承を使うと、共通の契約を段階的に拡張できます。
たとえば、保存できるエンティティを表すインターフェースを作ることもできます。
C#public interface ISavableEntity : IEntity
{
void Save();
}
実装クラスです。
C#public class Product : ISavableEntity
{
public int Id { get; set; }
public void Save()
{
Console.WriteLine($"商品ID {Id} を保存しました");
}
}
8-3. ジェネリックとインターフェースの組み合わせ
C#では、ジェネリックとインターフェースを組み合わせることもよくあります。
たとえば、リポジトリパターンで次のようなインターフェースを定義することがあります。
C#public interface IRepository<T>
{
void Add(T item);
T FindById(int id);
}
Tは型を表すプレースホルダーです。
ユーザー用のクラスを作ります。
C#public class User
{
public int Id { get; set; }
public string Name { get; set; }
}
ユーザー用リポジトリです。
C#public class UserRepository : IRepository<User>
{
private readonly List<User> _users = new List<User>();
public void Add(User item)
{
_users.Add(item);
}
public User FindById(int id)
{
return _users.FirstOrDefault(user => user.Id == id);
}
}
使い方です。
C#IRepository<User> repository = new UserRepository();
repository.Add(new User { Id = 1, Name = "田中" });
User user = repository.FindById(1);
Console.WriteLine(user.Name);
ジェネリックを使うと、Userだけでなく、ProductやOrderなど、さまざまな型に対応できる汎用的なインターフェースを作れます。
C#public class Product
{
public int Id { get; set; }
public string Name { get; set; }
}
C#public class ProductRepository : IRepository<Product>
{
private readonly List<Product> _products = new List<Product>();
public void Add(Product item)
{
_products.Add(item);
}
public Product FindById(int id)
{
return _products.FirstOrDefault(product => product.Id == id);
}
}
このように、ジェネリックとインターフェースを組み合わせると、再利用性の高い設計ができます。
8-4. 標準ライブラリで使われる代表的なインターフェース
C#の標準ライブラリにも、多くのインターフェースがあります。
代表的なものを見てみましょう。
| インターフェース | 役割 |
|---|---|
IEnumerable<T> | foreachで列挙できることを表す |
IDisposable | リソースを解放できることを表す |
IComparable<T> | 比較できることを表す |
IEquatable<T> | 等価比較できることを表す |
ICollection<T> | コレクションとして操作できることを表す |
IList<T> | リストとして操作できることを表す |
たとえば、IEnumerable<T>は、foreachで順番に取り出せることを表します。
C#IEnumerable<int> numbers = new List<int> { 1, 2, 3 };
foreach (int number in numbers)
{
Console.WriteLine(number);
}
List<T>はIEnumerable<T>を実装しているため、foreachで使えます。
IDisposableは、使い終わったリソースを解放するためのインターフェースです。
C#public class SampleResource : IDisposable
{
public void Dispose()
{
Console.WriteLine("リソースを解放しました");
}
}
using文と組み合わせると、処理が終わったタイミングで自動的にDisposeが呼ばれます。
C#using (var resource = new SampleResource())
{
Console.WriteLine("リソースを使用中");
}
このように、C#の標準ライブラリでもインターフェースは多く使われています。
8-5. C# 8.0以降のデフォルトインターフェースメソッド
C# 8.0以降では、インターフェースにメソッドのデフォルト実装を書けるようになりました。
これをデフォルトインターフェースメソッドと呼びます。
C#public interface ILogger
{
void Log(string message);
void LogError(string message)
{
Log($"ERROR: {message}");
}
}
この例では、Logメソッドは実装クラスで実装する必要があります。
一方、LogErrorメソッドにはデフォルトの処理が書かれています。
実装クラスです。
C#public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine(message);
}
}
使い方です。
C#ILogger logger = new ConsoleLogger();
logger.Log("通常ログ");
logger.LogError("エラーが発生しました");
ConsoleLoggerクラスではLogErrorを実装していませんが、インターフェース側のデフォルト実装を利用できます。
ただし、初心者のうちはデフォルトインターフェースメソッドを多用しすぎない方がよいです。
インターフェースの基本的な役割は、あくまで「契約を定義すること」です。
処理の共通化が主な目的なら、抽象クラスの方がわかりやすい場合もあります。
9. C#インターフェースのよくある質問
ここでは、C#インターフェースについて初心者が疑問に感じやすいポイントをQ&A形式で解説します。
9-1. インターフェースはなぜ必要なの?
インターフェースは、クラス同士の依存を減らし、実装を差し替えやすくするために使います。
たとえば、メール送信処理を直接使うコードでは、メール送信以外に変更しにくくなります。
C#public class NotificationService
{
private readonly EmailSender _sender = new EmailSender();
}
インターフェースを使うと、メール送信でもSMS送信でも差し替えられます。
C#public class NotificationService
{
private readonly IMessageSender _sender;
public NotificationService(IMessageSender sender)
{
_sender = sender;
}
}
つまり、インターフェースは「特定のクラスに依存しすぎない設計」を作るために必要です。
9-2. インターフェースと継承は何が違う?
継承は、「親クラスの性質や処理を子クラスが受け継ぐ」仕組みです。
C#public abstract class Animal
{
public void Eat()
{
Console.WriteLine("食べます");
}
}
C#public class Dog : Animal
{
}
一方、インターフェースは、「この機能を持っていること」を表す仕組みです。
C#public interface IRunnable
{
void Run();
}
C#public class Dog : IRunnable
{
public void Run()
{
Console.WriteLine("走ります");
}
}
継承は「is-a」の関係で使われることが多いです。
たとえば、「DogはAnimalである」という関係です。
インターフェースは「can-do」の関係で使われることが多いです。
たとえば、「Dogは走ることができる」という関係です。
9-3. インターフェースに処理を書ける?
C# 8.0以降では、インターフェースにデフォルト実装として処理を書けます。
C#public interface IGreeting
{
void SayHello()
{
Console.WriteLine("こんにちは");
}
}
ただし、従来の基本的な使い方では、インターフェースには処理内容を書かず、メソッドやプロパティの形だけを定義します。
C#public interface IGreeting
{
void SayHello();
}
初心者のうちは、まず「インターフェースは基本的に処理の形を決めるもの」と理解しましょう。
デフォルト実装は応用的な機能として覚えるとよいです。
9-4. privateやpublicなどのアクセス修飾子は書ける?
基本的なインターフェースメンバーは、外部から使う契約を表すため、通常はpublicなメンバーとして扱われます。
たとえば、次のように書きます。
C#public interface ILogger
{
void Log(string message);
}
実装クラスでは、通常publicとして実装します。
C#public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine(message);
}
}
初心者がよく書いてしまう間違いは、実装クラス側でpublicを付け忘れることです。
C#public class ConsoleLogger : ILogger
{
void Log(string message)
{
Console.WriteLine(message);
}
}
この書き方では、通常の暗黙的なインターフェース実装としては認められません。
正しくは次のようにします。
C#public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine(message);
}
}
また、明示的インターフェース実装の場合は、publicを付けずに次のように書きます。
C#public class ConsoleLogger : ILogger
{
void ILogger.Log(string message)
{
Console.WriteLine(message);
}
}
アクセス修飾子の扱いは少し複雑なので、初心者のうちは「普通に実装するときはpublicを付ける」と覚えておくとよいです。
9-5. インターフェースは初心者でも覚えるべき?
C#をしっかり学ぶなら、インターフェースは初心者のうちから覚えておくべき重要な機能です。
ただし、最初から完璧に理解する必要はありません。
まずは次の3点を押さえましょう。
| 覚えるポイント | 内容 |
|---|---|
| インターフェースは契約 | クラスが持つべきメソッドやプロパティを決める |
| 実装クラスが中身を書く | インターフェース自体は基本的に処理を書かない |
| インターフェース型で扱える | 実装クラスを差し替えやすくなる |
最初は簡単な例から始めるのがおすすめです。
C#public interface IGreeter
{
void Greet();
}
public class JapaneseGreeter : IGreeter
{
public void Greet()
{
Console.WriteLine("こんにちは");
}
}
public class EnglishGreeter : IGreeter
{
public void Greet()
{
Console.WriteLine("Hello");
}
}
使い方です。
C#IGreeter greeter = new JapaneseGreeter();
greeter.Greet();
greeter = new EnglishGreeter();
greeter.Greet();
このようなシンプルなコードで、「同じ呼び出し方でも実装を切り替えられる」という感覚をつかむことが大切です。
まとめ
C#のインターフェースは、クラスが実装すべきメソッドやプロパティの形を決める仕組みです。
インターフェース自体は、基本的に具体的な処理内容ではなく、「この機能を持っていること」という契約を定義します。
たとえば、次のようにインターフェースを定義できます。
C#public interface ILogger
{
void Log(string message);
}
そして、実装クラスで具体的な処理を書きます。
C#public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine(message);
}
}
インターフェースを使うメリットは、実装クラスを差し替えやすくなること、クラス同士の依存を減らせること、テストしやすいコードを書けることです。
C#ILogger logger = new ConsoleLogger();
このように、変数の型をインターフェースにすることで、呼び出し側は具体的なクラスを意識しなくてよくなります。
また、インターフェースと抽象クラスには違いがあります。
インターフェースは「何ができるか」を表す契約です。
抽象クラスは「共通の処理や状態を持つ親クラス」です。
| 使うもの | 向いている場面 |
|---|---|
| インターフェース | 実装を差し替えたい、複数の実装を同じように扱いたい |
| 抽象クラス | 共通処理や共通の状態を持たせたい |
初心者のうちは、まず次のように覚えるとよいです。
C#インターフェース = 何ができるかを決めるもの
実装クラス = 実際にどう動くかを書くもの
インターフェースは、DI、ポリモーフィズム、テスト、設計パターンなど、C#の実務的な開発で頻繁に登場します。
最初は少し抽象的に感じるかもしれませんが、サンプルコードを実際に書きながら、「同じインターフェースで複数のクラスを扱える」という感覚を身につけることが大切です。

