C# interfaceとは?使い方・継承との違い・実装例を初心者向けにわかりやすく解説
はじめに
C#でプログラムを書いていると、interfaceという言葉をよく見かけます。読み方は「インターフェイス」で、C#の設計を理解するうえでとても重要な機能です。
しかし初心者のうちは、「classと何が違うの?」「継承とどう使い分けるの?」「なぜわざわざinterfaceを作るの?」と疑問に感じることも多いでしょう。
C# interfaceは、クラスに「この機能を必ず持ってください」と約束させるための仕組みです。実装の中身ではなく、使い方のルールを定義するものだと考えると理解しやすくなります。
この記事では、C# interfaceの基本的な意味、書き方、実装例、継承や抽象クラスとの違い、実務での使い方まで、初心者向けにわかりやすく解説します。
1. C#のinterfaceとは?初心者向けに基本をわかりやすく解説
1-1. interfaceの意味と役割
C#のinterfaceとは、クラスが持つべき機能のルールを定義するための仕組みです。
たとえば、「保存できるもの」という共通ルールを作りたい場合、次のようにinterfaceを定義できます。
C#public interface ISavable
{
void Save();
}
このISavableは、「Saveメソッドを持っていること」を表すルールです。実際にどのように保存するかは、interfaceではなく実装するクラス側で決めます。
C#public class UserData : ISavable
{
public void Save()
{
Console.WriteLine("ユーザーデータを保存しました。");
}
}
つまり、interfaceは「何ができるか」を定義し、クラスは「どう実行するか」を実装します。
1-2. interfaceで定義できるもの
C# interfaceでは、主に次のようなメンバーを定義できます。
C#public interface IUserRepository
{
string Name { get; set; }
void AddUser(string name);
string FindUser(int id);
}
代表的なものは、メソッドとプロパティです。
メソッドは「何らかの処理」を表し、プロパティは「値の取得や設定」を表します。ほかにも、イベントやインデクサーなどを定義できます。
また、C# 8.0以降では、interface内にデフォルト実装を持つメソッドも書けるようになりました。ただし、初心者のうちはまず「interfaceはルールを定義するもの」と理解しておけば十分です。
1-3. interfaceを使うと何が便利になるのか
interfaceを使うと、プログラムの部品を交換しやすくなります。
たとえば、メール送信機能を考えてみましょう。
C#public interface IMessageSender
{
void Send(string message);
}
このinterfaceを使えば、メール送信、SMS送信、チャット送信など、異なる送信方法を同じ形で扱えます。
C#public class EmailSender : IMessageSender
{
public void Send(string message)
{
Console.WriteLine($"メール送信: {message}");
}
}
public class SmsSender : IMessageSender
{
public void Send(string message)
{
Console.WriteLine($"SMS送信: {message}");
}
}
使う側は、具体的にEmailSenderなのかSmsSenderなのかを強く意識せず、IMessageSenderとして扱えます。
C#public class NotificationService
{
private readonly IMessageSender _sender;
public NotificationService(IMessageSender sender)
{
_sender = sender;
}
public void Notify(string message)
{
_sender.Send(message);
}
}
このようにinterfaceを使うことで、処理の差し替えが簡単になり、保守しやすいコードを書けます。
1-4. interfaceがよく使われる場面
C# interfaceは、実務でも非常によく使われます。
特によく使われるのは、次のような場面です。
C#public interface ILogger
{
void Log(string message);
}
ログ出力、データベースアクセス、ファイル操作、外部API通信、メール送信、テスト用のモック作成などでよく使われます。
たとえば、ログ出力先をコンソールからファイルに変更したい場合でも、ILoggerというinterfaceを使っていれば、使う側のコードを大きく変更せずに済みます。
C#public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine(message);
}
}
public class FileLogger : ILogger
{
public void Log(string message)
{
// 実際にはファイルへ書き込む処理を書く
Console.WriteLine($"ファイルに出力: {message}");
}
}
interfaceは、変更に強い設計を作るための基本的な道具です。
2. C# interfaceの基本的な書き方
2-1. interfaceの宣言方法
C#でinterfaceを宣言するには、interfaceキーワードを使います。
C#public interface IAnimal
{
void Speak();
}
この例では、IAnimalというinterfaceを定義しています。中にはSpeakメソッドが宣言されています。
interface内のメソッドは、基本的に「このメソッドを実装してください」という意味になります。そのため、通常はメソッドの中身を書きません。
C#public interface IPrinter
{
void Print(string text);
}
このIPrinterを実装するクラスは、必ずPrintメソッドを持つ必要があります。
2-2. interfaceをクラスに実装する方法
interfaceをクラスに実装するには、クラス名の後にコロン:を書き、その後にinterface名を書きます。
C#public interface IAnimal
{
void Speak();
}
public class Dog : IAnimal
{
public void Speak()
{
Console.WriteLine("ワン!");
}
}
DogクラスはIAnimalを実装しています。そのため、IAnimalに定義されているSpeakメソッドを必ず実装しなければなりません。
もしSpeakメソッドを実装しないと、コンパイルエラーになります。
C#public class Cat : IAnimal
{
public void Speak()
{
Console.WriteLine("ニャー!");
}
}
このように、複数のクラスで同じinterfaceを実装できます。
2-3. メソッド・プロパティを定義する例
interfaceには、メソッドだけでなくプロパティも定義できます。
C#public interface IProduct
{
string Name { get; set; }
int Price { get; set; }
void Display();
}
このIProductを実装するクラスは、Name、Price、Displayを実装する必要があります。
C#public class Book : IProduct
{
public string Name { get; set; }
public int Price { get; set; }
public void Display()
{
Console.WriteLine($"{Name}: {Price}円");
}
}
使う側は次のように書けます。
C#Book book = new Book
{
Name = "C#入門",
Price = 2500
};
book.Display();
interfaceは、クラスに必要なメソッドやプロパティの形を決めるために使います。
2-4. interface名に「I」を付ける命名規則
C#では、interface名の先頭にIを付ける命名規則がよく使われます。
C#public interface ILogger
{
void Log(string message);
}
public interface IRepository
{
void Save();
}
public interface IDisposable
{
void Dispose();
}
Iはinterfaceの頭文字です。
たとえば、Loggerというクラスに対して、interfaceはILoggerという名前にします。
C#public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine(message);
}
}
この命名規則はC#の標準的な文化として広く使われています。必須ではありませんが、初心者のうちはIを付けるルールに従うのがおすすめです。
3. C# interfaceの実装例
3-1. シンプルなinterfaceの実装例
まずは、もっともシンプルなC# interfaceの実装例を見てみましょう。
C#public interface IGreeting
{
void SayHello();
}
このinterfaceは、「あいさつできる」という機能を表しています。
次に、このinterfaceを実装するクラスを作ります。
C#public class JapaneseGreeting : IGreeting
{
public void SayHello()
{
Console.WriteLine("こんにちは");
}
}
実際に使うコードは次のとおりです。
C#JapaneseGreeting greeting = new JapaneseGreeting();
greeting.SayHello();
実行結果は次のようになります。
C#こんにちは
interfaceを実装するクラスでは、interfaceに定義されたメソッドを必ず書く必要があります。
3-2. 複数のクラスで同じinterfaceを実装する例
interfaceの大きな特徴は、複数のクラスで同じルールを共有できることです。
C#public interface IPayment
{
void Pay(int amount);
}
このIPaymentは、「支払いができる」というルールを表します。
C#public class CreditCardPayment : IPayment
{
public void Pay(int amount)
{
Console.WriteLine($"クレジットカードで{amount}円支払いました。");
}
}
public class CashPayment : IPayment
{
public void Pay(int amount)
{
Console.WriteLine($"現金で{amount}円支払いました。");
}
}
public class PayPayPayment : IPayment
{
public void Pay(int amount)
{
Console.WriteLine($"PayPayで{amount}円支払いました。");
}
}
どのクラスもIPaymentを実装しているため、共通してPayメソッドを持ちます。
このように、支払い方法が違っても、同じinterfaceを通して扱えるようになります。
3-3. interface型の変数として扱う例
interfaceを使うと、実装クラスのインスタンスをinterface型の変数に代入できます。
C#IPayment payment = new CreditCardPayment();
payment.Pay(3000);
ここで重要なのは、変数の型がCreditCardPaymentではなくIPaymentになっている点です。
C#payment = new CashPayment();
payment.Pay(1000);
同じpayment変数に、別の実装クラスを代入できます。
これは、使う側が具体的なクラスではなく、interfaceに依存しているからです。
C#public class PaymentService
{
public void Checkout(IPayment payment, int amount)
{
payment.Pay(amount);
}
}
このPaymentServiceは、クレジットカードでも現金でもPayPayでも使えます。
C#PaymentService service = new PaymentService();
service.Checkout(new CreditCardPayment(), 5000);
service.Checkout(new CashPayment(), 2000);
service.Checkout(new PayPayPayment(), 1500);
interface型で扱うことで、柔軟性の高いコードになります。
3-4. 実務で使いやすいサンプルコード
実務でよくある例として、ログ出力のinterfaceを考えてみます。
C#public interface ILogger
{
void Log(string message);
}
コンソールにログを出力するクラスを作ります。
C#public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine($"[LOG] {message}");
}
}
次に、ILoggerを使うサービスクラスを作ります。
C#public class OrderService
{
private readonly ILogger _logger;
public OrderService(ILogger logger)
{
_logger = logger;
}
public void CreateOrder(string productName)
{
_logger.Log($"{productName}の注文を作成しました。");
}
}
実行例です。
C#ILogger logger = new ConsoleLogger();
OrderService orderService = new OrderService(logger);
orderService.CreateOrder("ノートPC");
この設計にしておくと、あとからファイル出力用のログクラスに差し替えることも簡単です。
C#public class FileLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine($"ファイルにログを書き込み: {message}");
}
}
使う側は次のように変更するだけです。
C#ILogger logger = new FileLogger();
OrderService orderService = new OrderService(logger);
OrderServiceの中身を変更せずにログ出力方法を変えられる点が、interfaceを使う大きなメリットです。
4. interfaceと継承の違い
4-1. クラス継承とは何か
C#のクラス継承とは、あるクラスの機能を別のクラスが引き継ぐ仕組みです。
C#public class Animal
{
public void Eat()
{
Console.WriteLine("食べます");
}
}
このAnimalクラスを継承して、Dogクラスを作れます。
C#public class Dog : Animal
{
public void Bark()
{
Console.WriteLine("吠えます");
}
}
DogクラスはAnimalクラスを継承しているため、Eatメソッドを使えます。
C#Dog dog = new Dog();
dog.Eat();
dog.Bark();
継承は、親クラスの共通機能を子クラスで再利用したいときに使います。
4-2. interfaceと継承の役割の違い
interfaceと継承は、どちらもクラス設計で使われますが、役割が異なります。
継承は「共通の実装を引き継ぐ」ための仕組みです。一方、interfaceは「共通のルールを定義する」ための仕組みです。
C#public class Animal
{
public void Eat()
{
Console.WriteLine("食べます");
}
}
public interface IRunnable
{
void Run();
}
Animalは「動物としての共通処理」を持っています。IRunnableは「走れる」という機能のルールだけを表しています。
C#public class Dog : Animal, IRunnable
{
public void Run()
{
Console.WriteLine("犬が走ります");
}
}
この例では、DogはAnimalを継承し、さらにIRunnableを実装しています。
継承は「何であるか」、interfaceは「何ができるか」を表すと理解するとわかりやすいです。
4-3. 「is-a」と「can-do」で考える使い分け
interfaceと継承の使い分けは、「is-a」と「can-do」で考えると理解しやすくなります。
継承は「is-a」の関係です。
C#public class Dog : Animal
{
}
これは「Dog is an Animal」、つまり「犬は動物である」という関係です。
一方、interfaceは「can-do」の関係です。
C#public interface IFlyable
{
void Fly();
}
これは「飛べる」という能力を表しています。
C#public class Bird : IFlyable
{
public void Fly()
{
Console.WriteLine("鳥が飛びます");
}
}
public class Airplane : IFlyable
{
public void Fly()
{
Console.WriteLine("飛行機が飛びます");
}
}
鳥と飛行機はまったく別のものですが、どちらも「飛べる」という共通の能力を持っています。
このような場合は、クラス継承よりもinterfaceが向いています。
4-4. C#で多重継承できない理由とinterfaceの関係
C#では、クラスの多重継承はできません。つまり、1つのクラスが複数のクラスを同時に継承することはできません。
C#public class A
{
}
public class B
{
}
// C#ではこれはできない
// public class C : A, B
// {
// }
多重継承を許すと、同じ名前のメソッドが複数の親クラスにあった場合、どちらを使うべきか曖昧になることがあります。
一方、C#では複数のinterfaceを実装できます。
C#public interface IPrintable
{
void Print();
}
public interface IScannable
{
void Scan();
}
public class MultiFunctionPrinter : IPrintable, IScannable
{
public void Print()
{
Console.WriteLine("印刷します");
}
public void Scan()
{
Console.WriteLine("スキャンします");
}
}
このように、クラスは1つのクラスしか継承できませんが、複数のinterfaceは実装できます。
そのため、C# interfaceは多重継承の代わりに、複数の能力をクラスに持たせる手段としてよく使われます。
5. interfaceと抽象クラスの違い
5-1. 抽象クラスとは何か
抽象クラスとは、直接インスタンス化できないクラスのことです。abstractキーワードを使って定義します。
C#public abstract class Animal
{
public void Eat()
{
Console.WriteLine("食べます");
}
public abstract void Speak();
}
抽象クラスには、実装済みのメソッドと、子クラスで必ず実装する抽象メソッドを含めることができます。
C#public class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("ワン!");
}
}
抽象クラスは、共通処理を持たせつつ、一部の処理を子クラスに任せたいときに使います。
5-2. interfaceと抽象クラスの比較表
interfaceと抽象クラスの違いを整理すると、次のようになります。
| 項目 | interface | 抽象クラス |
|---|---|---|
| 主な役割 | 機能のルールを定義する | 共通の土台を作る |
| インスタンス化 | できない | できない |
| 実装の共有 | 基本はしない | しやすい |
| 複数指定 | 複数実装できる | クラス継承は1つだけ |
| フィールド | 通常のインスタンスフィールドは持てない | 持てる |
| コンストラクタ | 基本的に書けない | 書ける |
| 向いている用途 | 能力や契約の定義 | 共通処理の再利用 |
初心者のうちは、interfaceは「ルール」、抽象クラスは「共通処理を持つ親クラス」と考えると理解しやすいです。
5-3. interfaceを選ぶべきケース
interfaceを選ぶべきなのは、複数の異なるクラスに共通の能力を持たせたい場合です。
C#public interface IDownloadable
{
void Download();
}
たとえば、画像、動画、PDFファイルなど、種類は違っても「ダウンロードできる」という共通点があります。
C#public class ImageFile : IDownloadable
{
public void Download()
{
Console.WriteLine("画像をダウンロードします");
}
}
public class PdfFile : IDownloadable
{
public void Download()
{
Console.WriteLine("PDFをダウンロードします");
}
}
このように、継承関係はないけれど共通の機能を持たせたい場合は、interfaceが向いています。
また、テストしやすい設計にしたい場合や、DIを使いたい場合にもinterfaceがよく使われます。
5-4. 抽象クラスを選ぶべきケース
抽象クラスを選ぶべきなのは、複数のクラスで共通の処理や状態を持たせたい場合です。
C#public abstract class Report
{
public string Title { get; set; }
public void PrintTitle()
{
Console.WriteLine($"レポート名: {Title}");
}
public abstract void Generate();
}
この例では、Titleという共通プロパティと、PrintTitleという共通処理を抽象クラスに持たせています。
C#public class SalesReport : Report
{
public override void Generate()
{
Console.WriteLine("売上レポートを作成します");
}
}
共通の処理をまとめて再利用したい場合は、interfaceより抽象クラスのほうが向いていることがあります。
ただし、C#ではクラス継承は1つしかできないため、抽象クラスを使うと継承の自由度は下がります。
6. C# interfaceの応用的な使い方
6-1. 複数のinterfaceを実装する方法
C#では、1つのクラスが複数のinterfaceを実装できます。
C#public interface IPrintable
{
void Print();
}
public interface IFaxable
{
void Fax();
}
public interface IScannable
{
void Scan();
}
複数のinterfaceを実装する場合は、カンマで区切って書きます。
C#public class OfficePrinter : IPrintable, IFaxable, IScannable
{
public void Print()
{
Console.WriteLine("印刷します");
}
public void Fax()
{
Console.WriteLine("FAXを送信します");
}
public void Scan()
{
Console.WriteLine("スキャンします");
}
}
このように、1つのクラスに複数の役割を持たせられます。
ただし、あまりにも多くのinterfaceを実装しているクラスは、責任が大きくなりすぎている可能性があります。設計を見直すサインになることもあります。
6-2. interfaceを継承する方法
interfaceは、別のinterfaceを継承できます。
C#public interface IReadable
{
void Read();
}
public interface IWritable
{
void Write();
}
public interface IFile : IReadable, IWritable
{
void Open();
}
IFileは、IReadableとIWritableを継承しています。
そのため、IFileを実装するクラスは、Read、Write、Openをすべて実装する必要があります。
C#public class TextFile : IFile
{
public void Read()
{
Console.WriteLine("読み込みます");
}
public void Write()
{
Console.WriteLine("書き込みます");
}
public void Open()
{
Console.WriteLine("ファイルを開きます");
}
}
interfaceを継承すると、複数の小さな役割をまとめた大きなinterfaceを作ることができます。
6-3. 明示的インターフェイス実装
明示的インターフェイス実装とは、interfaceのメンバーをクラスの通常メンバーとして公開せず、interface経由でのみ使えるようにする実装方法です。
C#public interface IPrinter
{
void Print();
}
public interface IReportPrinter
{
void Print();
}
このように、複数のinterfaceに同じ名前のメソッドがある場合を考えます。
C#public class Printer : IPrinter, IReportPrinter
{
void IPrinter.Print()
{
Console.WriteLine("通常印刷します");
}
void IReportPrinter.Print()
{
Console.WriteLine("レポートを印刷します");
}
}
使うときは、interface型に代入して呼び出します。
C#Printer printer = new Printer();
IPrinter normalPrinter = printer;
normalPrinter.Print();
IReportPrinter reportPrinter = printer;
reportPrinter.Print();
明示的インターフェイス実装を使うと、同じメソッド名でもinterfaceごとに別の動作を定義できます。
ただし、初心者のうちは少し難しく感じるかもしれません。通常のinterface実装に慣れてから理解すれば問題ありません。
6-4. default interface methodsの概要
C# 8.0以降では、default interface methodsという機能により、interface内にメソッドのデフォルト実装を書けるようになりました。
C#public interface ILogger
{
void Log(string message);
void LogError(string message)
{
Log($"ERROR: {message}");
}
}
この例では、LogErrorメソッドに実装があります。
C#public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine(message);
}
}
ConsoleLoggerはLogだけを実装していますが、LogErrorはinterface側のデフォルト実装を利用できます。
C#ILogger logger = new ConsoleLogger();
logger.LogError("エラーが発生しました");
default interface methodsは、既存のinterfaceに新しいメソッドを追加したい場合などに役立ちます。
ただし、使いすぎるとinterfaceの役割が曖昧になりやすいため、初心者のうちは「interfaceにも一部実装を書ける場合がある」程度に理解しておけば十分です。
7. interfaceを使うメリット・デメリット
7-1. メリット:保守性が高くなる
interfaceを使う最大のメリットの1つは、保守性が高くなることです。
たとえば、次のように具体的なクラスに直接依存しているコードを考えます。
C#public class UserService
{
private readonly SqlUserRepository _repository;
public UserService()
{
_repository = new SqlUserRepository();
}
}
この書き方だと、SqlUserRepository以外に差し替えたい場合、UserServiceの中身を変更する必要があります。
一方、interfaceを使うと次のように書けます。
C#public interface IUserRepository
{
void Save(string userName);
}
public class UserService
{
private readonly IUserRepository _repository;
public UserService(IUserRepository repository)
{
_repository = repository;
}
public void Register(string userName)
{
_repository.Save(userName);
}
}
この設計なら、データベース保存、ファイル保存、テスト用保存などに差し替えやすくなります。
C#public class SqlUserRepository : IUserRepository
{
public void Save(string userName)
{
Console.WriteLine($"DBに保存: {userName}");
}
}
public class FileUserRepository : IUserRepository
{
public void Save(string userName)
{
Console.WriteLine($"ファイルに保存: {userName}");
}
}
実装クラスを変更しても、UserServiceのコードはそのまま使えます。
7-2. メリット:テストしやすくなる
interfaceを使うと、単体テストがしやすくなります。
たとえば、メール送信を行う処理をテストするとき、実際にメールを送ってしまうと困る場合があります。
C#public interface IEmailSender
{
void Send(string to, string message);
}
本番用の実装は次のようになります。
C#public class EmailSender : IEmailSender
{
public void Send(string to, string message)
{
Console.WriteLine($"{to}へメール送信: {message}");
}
}
テスト用の実装を作ることもできます。
C#public class FakeEmailSender : IEmailSender
{
public void Send(string to, string message)
{
Console.WriteLine($"テスト用メール送信: {to}, {message}");
}
}
使う側はinterfaceに依存します。
C#public class AccountService
{
private readonly IEmailSender _emailSender;
public AccountService(IEmailSender emailSender)
{
_emailSender = emailSender;
}
public void CreateAccount(string email)
{
_emailSender.Send(email, "アカウントを作成しました。");
}
}
テスト時にはFakeEmailSenderを渡せば、実際のメール送信をせずに動作確認できます。
7-3. メリット:依存関係を分離できる
interfaceを使うと、クラス同士の依存関係を分離できます。
悪い例として、具体的なクラスに直接依存するコードを見てみましょう。
C#public class ReportService
{
private readonly PdfExporter _exporter = new PdfExporter();
public void Export()
{
_exporter.Export();
}
}
この場合、ReportServiceはPdfExporterに強く依存しています。
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();
}
}
PDF出力だけでなく、CSV出力やExcel出力にも対応しやすくなります。
C#public class PdfExporter : IExporter
{
public void Export()
{
Console.WriteLine("PDFで出力します");
}
}
public class CsvExporter : IExporter
{
public void Export()
{
Console.WriteLine("CSVで出力します");
}
}
このようにinterfaceは、クラス同士をゆるく結びつけるために役立ちます。
7-4. デメリット:設計が複雑になる場合がある
interfaceは便利ですが、何でもinterfaceにすればよいわけではありません。
たとえば、1つのクラスでしか使わない処理に対して、無理にinterfaceを作ると、ファイル数やコード量が増えてしまいます。
C#public interface IUserNameFormatter
{
string Format(string name);
}
public class UserNameFormatter : IUserNameFormatter
{
public string Format(string name)
{
return name.Trim();
}
}
このような小さな処理に対して常にinterfaceを作ると、かえって読みづらくなることがあります。
interfaceを使うべきかどうかは、「差し替える可能性があるか」「複数の実装があるか」「テストで置き換えたいか」を基準に考えるとよいでしょう。
8. interfaceを使うときの注意点
8-1. 何でもinterface化しない
初心者がやりがちなミスの1つが、すべてのクラスに対してinterfaceを作ってしまうことです。
C#public interface IUserService
{
void Register();
}
public class UserService : IUserService
{
public void Register()
{
Console.WriteLine("登録します");
}
}
この設計自体は悪くありません。しかし、実装が1つしかなく、差し替え予定もなく、テストでも不要な場合は、interfaceが過剰になることがあります。
interfaceは便利な道具ですが、使いすぎるとコードの見通しが悪くなります。
「本当に抽象化する必要があるか」を考えてから使うことが大切です。
8-2. 役割ごとに小さく分ける
interfaceは、役割ごとに小さく分けるのが基本です。
悪い例を見てみましょう。
C#public interface IMachine
{
void Print();
void Scan();
void Fax();
void Staple();
}
このinterfaceを実装するクラスは、すべてのメソッドを実装しなければなりません。
もしスキャンしかできない機械にこのinterfaceを実装すると、不要なメソッドまで実装することになります。
よい例は、役割ごとに分けることです。
C#public interface IPrintable
{
void Print();
}
public interface IScannable
{
void Scan();
}
public interface IFaxable
{
void Fax();
}
必要な機能だけを実装できます。
C#public class SimplePrinter : 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に分けることで、使いやすく保守しやすい設計になります。
8-3. 実装クラスに依存しすぎない
interfaceを使っていても、実装クラスに依存しすぎると意味が薄れてしまいます。
たとえば、次のようなコードです。
C#public class UserService
{
private readonly IUserRepository _repository;
public UserService()
{
_repository = new SqlUserRepository();
}
}
変数の型はIUserRepositoryですが、コンストラクタ内でSqlUserRepositoryを直接作っています。
これだと、結局UserServiceはSqlUserRepositoryに依存しています。
よりよい書き方は、外からinterfaceを渡す方法です。
C#public class UserService
{
private readonly IUserRepository _repository;
public UserService(IUserRepository repository)
{
_repository = repository;
}
}
このようにすると、SqlUserRepositoryでもMockUserRepositoryでも渡せます。
C#IUserRepository repository = new SqlUserRepository();
UserService service = new UserService(repository);
interfaceを使うときは、実装クラスを直接作る場所をできるだけ分離することが大切です。
8-4. 初心者がよくやりがちなミス
C# interfaceで初心者がよくやりがちなミスをいくつか紹介します。
まず、interfaceのメソッドを実装し忘れるミスです。
C#public interface IWorker
{
void Work();
}
// Workメソッドがないのでエラーになる
public class Engineer : IWorker
{
}
IWorkerを実装するなら、Workメソッドを書く必要があります。
C#public class Engineer : IWorker
{
public void Work()
{
Console.WriteLine("開発します");
}
}
次に、アクセス修飾子を間違えるミスです。
interfaceのメソッドを通常実装する場合は、publicにする必要があります。
C#public class Designer : IWorker
{
public void Work()
{
Console.WriteLine("デザインします");
}
}
また、interfaceそのものをnewしようとするミスもあります。
C#// これはできない
// IWorker worker = new IWorker();
interfaceはルールであり、実体ではありません。インスタンス化するには、interfaceを実装したクラスが必要です。
C#IWorker worker = new Engineer();
worker.Work();
9. C# interfaceに関するよくある質問
9-1. interfaceにフィールドは定義できる?
C# interfaceには、通常のインスタンスフィールドは定義できません。
次のようなコードは書けません。
C#public interface IUser
{
// 通常のインスタンスフィールドは定義できない
// string name;
}
値を持たせたい場合は、プロパティを定義します。
C#public interface IUser
{
string Name { get; set; }
}
実装クラス側でプロパティを実装します。
C#public class User : IUser
{
public string Name { get; set; }
}
なお、C#ではinterfaceに定数を定義することはできます。
C#public interface ISettings
{
const int MaxCount = 100;
}
ただし、定数をinterfaceに置く設計は乱用しないほうがよいでしょう。初心者のうちは、interfaceにはメソッドやプロパティを定義するものと考えるのがおすすめです。
9-2. interfaceにコンストラクタは書ける?
通常、interfaceにコンストラクタは書けません。
コンストラクタは、クラスのインスタンスを作るときに実行される特別な処理です。
しかし、interfaceはインスタンス化するものではないため、通常のコンストラクタを持つことはできません。
C#public interface IService
{
// コンストラクタは書けない
// public IService() { }
}
初期化処理が必要な場合は、実装クラス側のコンストラクタに書きます。
C#public class MyService : IService
{
public MyService()
{
Console.WriteLine("初期化しました");
}
}
interfaceは「どのように作るか」ではなく、「何ができるか」を定義するものです。
9-3. interfaceはインスタンス化できる?
interfaceそのものはインスタンス化できません。
C#// これはエラー
// ILogger logger = new ILogger();
interfaceはあくまでルールであり、実際の処理を持つ実体ではないからです。
ただし、interface型の変数に、実装クラスのインスタンスを入れることはできます。
C#public interface ILogger
{
void Log(string message);
}
public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine(message);
}
}
使うときは次のように書きます。
C#ILogger logger = new ConsoleLogger();
logger.Log("ログを出力します");
このように、interface型として扱うことはできますが、newするのは実装クラスです。
9-4. interfaceとDIの関係は?
DIとは、Dependency Injectionの略で、日本語では「依存性の注入」と呼ばれます。
簡単にいうと、クラスが必要とする部品を自分で作るのではなく、外から渡してもらう仕組みです。
interfaceはDIと非常に相性がよいです。
C#public interface IUserRepository
{
void Save(string name);
}
C#public class SqlUserRepository : IUserRepository
{
public void Save(string name)
{
Console.WriteLine($"DBに保存: {name}");
}
}
サービスクラスでは、具体的なSqlUserRepositoryではなく、IUserRepositoryに依存します。
C#public class UserService
{
private readonly IUserRepository _repository;
public UserService(IUserRepository repository)
{
_repository = repository;
}
public void Register(string name)
{
_repository.Save(name);
}
}
このようにしておくと、本番ではSqlUserRepositoryを渡し、テストではテスト用の実装を渡すことができます。
C#public class FakeUserRepository : IUserRepository
{
public void Save(string name)
{
Console.WriteLine($"テスト用保存: {name}");
}
}
DIを使うことで、クラス同士の結びつきが弱くなり、変更やテストがしやすい設計になります。
まとめ
C# interfaceとは、クラスが持つべき機能のルールを定義するための仕組みです。実装の中身ではなく、「このメソッドやプロパティを持っていること」を約束させる役割があります。
interfaceを使うと、複数のクラスを同じ型として扱えるようになり、処理の差し替えが簡単になります。ログ出力、データ保存、メール送信、外部API通信、テスト用モックなど、実務でも幅広く使われます。
継承は「is-a」、つまり「何であるか」を表す関係です。一方、interfaceは「can-do」、つまり「何ができるか」を表す関係です。犬は動物であるという関係には継承が向いており、鳥も飛行機も飛べるという関係にはinterfaceが向いています。
また、抽象クラスは共通処理や状態を持たせたい場合に便利です。interfaceは複数実装でき、抽象クラスは共通実装を持たせやすいという違いがあります。
C# interfaceを使うと、保守性が高くなり、テストしやすくなり、依存関係を分離しやすくなります。ただし、何でもinterface化すると設計が複雑になるため、必要な場面で適切に使うことが大切です。
初心者のうちは、まず次の3点を押さえておきましょう。
1つ目は、interfaceは「機能のルール」を定義するものだということです。
2つ目は、interfaceを実装するクラスは、定義されたメソッドやプロパティを必ず実装する必要があるということです。
3つ目は、interfaceを使うと、具体的なクラスに依存しすぎない柔軟なコードを書けるということです。
C# interfaceは、最初は少し抽象的で難しく感じるかもしれません。しかし、実装例を何度か書いてみると、「なぜ便利なのか」が少しずつ見えてきます。C#で保守しやすいコードを書きたいなら、interfaceは必ず理解しておきたい重要な機能です。

