閉じる

【C#】インターフェース(interface)とは?使い方を基礎から解説

インターフェース(interface)は、クラスが「何ができるか」という契約を表す仕組みです。

具体的な処理の中身は書かず、メソッドやプロパティの“形”だけを定義します。

これにより、実装を差し替えても同じ使い方ができるようになり、テストしやすく拡張しやすい設計を実現できます。

C#のインターフェース(interface)とは

契約としての役割とメリット

インターフェースは、「この名前のメソッドやプロパティを必ず持つ」という約束(契約)を表します。

たとえばIPlayableというインターフェースにPlay()というメソッドが宣言されていれば、IPlayableを実装するすべてのクラスはPlay()を必ず提供しなければなりません。

この契約によって、次のようなメリットがあります。

  • 実装の交換が容易になります。インターフェース型で受け取れば、実際のクラスが何であっても同じメソッド名で呼び出せるため、差し替えが簡単です。
  • テストがしやすくなります。本物のクラスの代わりにテスト用の<cxst-typo>モック</cxst-typo>(模擬実装)を渡すだけで動作確認ができます。
  • 設計が明確になります。クラスの責務(役割)をインターフェース名に表現でき、後から見返しても理解しやすくなります。

できることとできないこと

インターフェースの本質は「形の宣言」であり、状態(値)は持ちません。

以下の表で違いを整理します。

項目できることできないこと
メンバーの種類メソッド、プロパティ、インデクサ、イベントの宣言フィールドの保持(状態の格納)
実装メンバーの“存在”を要求する具体的な処理の中身(通常はクラス側で実装)
アクセス修飾メンバーは暗黙にpublicprotectedやprivateの指定
継承複数のインターフェースを継承できるクラスのような多重継承は不可
注意

近年のC#では「既定実装」などの高度な機能も存在しますが、初心者向けの基本では契約のみを宣言する使い方に集中すると理解しやすいです。

よくある利用シーン(ポリモーフィズムの実現)

ポリモーフィズム(多態性)とは、同じ呼び出しでも中身の動作を差し替えられる性質です。

たとえばIPlayableという契約に従えば、音楽でもポッドキャストでもPlay()で再生できます。

呼び出し側はIPlayableしか知りませんが、実際にはさまざまなクラスが差し替え可能です。

interfaceの定義と書き方(C#基本)

基本構文とコード例

インターフェースはinterfaceキーワードで宣言します。

次の例は「再生できるもの」という契約を定義しています。

C#
// IPlayable は「再生できる」ことを表す契約です。
// 実装クラスは Title と Duration プロパティ、および Play メソッドを提供します。
public interface IPlayable
{
    string Title { get; }            // 読み取り専用プロパティ
    TimeSpan Duration { get; }       // 読み取り専用プロパティ
    void Play();                     // 再生を開始するメソッド
}

メソッドとプロパティの宣言方法

インターフェースでは中身(本体)は書きません

;で終わる宣言だけを並べます。

プロパティは{ get; }{ get; set; }の形で「取得だけ」「取得と設定」を指定します。

C#
// 読み取り専用と読み書き可能の例
public interface IConfigurable
{
    string Name { get; set; }     // 読み書き可能プロパティ
    int Version { get; }          // 読み取り専用プロパティ
    void Apply();                 // 設定を適用するメソッド
}

ポイント: インターフェースにgetしか書かれていない場合、実装クラスは「読み取り専用」として実装する必要があります。

読み書き可能が必要な場合はsetも宣言します。

命名規則(Iから始める)

C#の慣習では、インターフェース名は必ず大文字のIから始めるようにします。

たとえばIPlayableILoggerIRepositoryなどです。

これにより、コードを読んだときに「これはインターフェースだ」とすぐ分かります。

クラスでの実装と使い方

classでの実装手順

クラスがインターフェースを実装するには:の後ろにインターフェース名を列挙します。

すべてのメンバーを必ず実装してください。

C#
using System;
using System.Collections.Generic;

// 「再生できるもの」の契約
public interface IPlayable
{
    string Title { get; }
    TimeSpan Duration { get; }
    void Play();
}

// 曲を表すクラス(インスタンスは参照型)
public class Song : IPlayable
{
    // 自動実装プロパティ。コンストラクタで初期化します。
    public string Title { get; }
    public TimeSpan Duration { get; }

    public Song(string title, TimeSpan duration)
    {
        Title = title;      // コンストラクタで初期化
        Duration = duration;
    }

    public void Play()
    {
        // 実際の再生処理の代わりにコンソール出力
        Console.WriteLine($"Playing Song: {Title} ({Duration:mm\\:ss})");
    }
}

// ポッドキャストを表すクラス
public class Podcast : IPlayable
{
    public string Title { get; }
    public TimeSpan Duration { get; }

    public string Host { get; }  // インターフェース外の独自プロパティもOK

    public Podcast(string title, string host, TimeSpan duration)
    {
        Title = title;
        Host = host;
        Duration = duration;
    }

    public void Play()
    {
        Console.WriteLine($"Playing Podcast: {Title} by {Host} ({Duration:mm\\:ss})");
    }
}

public class Program
{
    // IPlayable型で受け取れば、SongでもPodcastでも扱えます。
    static void StartPlayback(IPlayable item)
    {
        item.Play();  // 契約にあるメンバーだけにアクセスできる
    }

    public static void Main()
    {
        var s = new Song("Hello, World", TimeSpan.FromSeconds(185));
        var p = new Podcast("Tech Talk", "Alice", TimeSpan.FromMinutes(42));

        StartPlayback(s);
        StartPlayback(p);

        // コレクションでも同様に扱えます
        var playlist = new List<IPlayable> { s, p };
        foreach (var x in playlist)
        {
            x.Play();
        }
    }
}
実行結果
Playing Song: Hello, World (03:05)
Playing Podcast: Tech Talk by Alice (42:00)
Playing Song: Hello, World (03:05)
Playing Podcast: Tech Talk by Alice (42:00)

interface型で受け取って使う

上のStartPlayback(IPlayable item)のように、引数や戻り値をインターフェース型にすると、呼び出し側は具象クラスを意識せずに使えます。

依存を「契約」に向ける設計は柔軟性を高め、テストや拡張を容易にします。

複数のinterfaceを実装する(多重実装)

クラスは複数のインターフェースを同時に実装できます。

これは「多重継承」とは異なり、契約を複数満たすという意味です。

C#
using System;

public interface IPlayable
{
    void Play();
}

public interface IStoppable
{
    void Stop();
}

// 両方の契約を満たすプレーヤー
public class MediaPlayer : IPlayable, IStoppable
{
    private bool _isPlaying;

    public void Play()
    {
        _isPlaying = true;
        Console.WriteLine("MediaPlayer: Play");
    }

    public void Stop()
    {
        _isPlaying = false;
        Console.WriteLine("MediaPlayer: Stop");
    }
}

public class Program2
{
    public static void Main()
    {
        var player = new MediaPlayer();

        // IPlayable として扱う
        IPlayable p = player;
        p.Play();

        // IStoppable として扱う
        IStoppable s = player;
        s.Stop();
    }
}
実行結果
MediaPlayer: Play
MediaPlayer: Stop

インスタンスの差し替えで柔軟にする

インターフェースを返すファクトリや、インターフェース型で保持する設計にすると、実装を簡単に差し替えできます。

以下は同じIPlayableの契約で別の実装に切り替える例です。

C#
using System;

public interface IPlayable
{
    void Play();
}

public class LocalFilePlayer : IPlayable
{
    public void Play() => Console.WriteLine("Play from local file");
}

public class StreamingPlayer : IPlayable
{
    public void Play() => Console.WriteLine("Play via streaming");
}

public static class PlayerFactory
{
    // 設定値などに応じて返す実装を切り替え
    public static IPlayable Create(bool useStreaming)
        => useStreaming ? new StreamingPlayer() : new LocalFilePlayer();
}

public class Program3
{
    public static void Main()
    {
        IPlayable player = PlayerFactory.Create(useStreaming: false);
        player.Play();  // Local

        // 設定変更で実装を差し替え
        player = PlayerFactory.Create(useStreaming: true);
        player.Play();  // Streaming
    }
}
実行結果
Play from local file
Play via streaming

基本ルールと注意点

フィールドは持てない(メソッド/プロパティのみ)

インターフェースは状態(フィールド)を持ちません

保持したい値は、実装クラスのフィールドやプロパティとして定義してください。

インターフェースでは「値の入れ物」ではなく、「操作の形」だけを定義します。

メンバーは常にpublic

インターフェースに書くメソッドやプロパティは常にpublicで、アクセス修飾子は書けません。

これは「契約を外部に公開し、だれでもその契約で扱える」ためです。

補足

新しめのC#には例外的な高度機能もありますが、基本設計では「publicの契約」として覚えるのが安全です。

メンバー追加は影響大(互換性に注意)

インターフェースに新しいメンバーを追加すると、既存のすべての実装クラスでコンパイルエラーになります。

契約が増えると、それを満たすための実装が必要になるからです。

既存コードへの影響が大きいので、公開済みのインターフェースを安易に変更しないことが重要です。

C#
// ある時点の契約
public interface ILogger
{
    void Log(string message);
}

// 後から追加(要注意)
public interface ILogger
{
    void Log(string message);
    // これを追加すると、すべての実装クラスに Error を実装する修正が必要
    void Error(string message);
}

役割ごとに小さく分けて設計する

1つのインターフェースに多くのメンバーを詰め込むと、実装負担が増え、再利用もしにくくなります。

役割ごとに小さく分けると扱いやすくなります。

たとえばIPlayableIStoppableを分ければ、再生しかできない実装や停止しか関与しないコンポーネントを柔軟に作れます。

これを「インターフェース分離の原則」と呼びます。

まとめ

インターフェースは実装すべき機能の契約を明示し、クラスの差し替えやテストを容易にする強力な仕組みです。

C#ではinterfaceでメソッドとプロパティの形を宣言し、クラスがそれを実装します。

命名はIから始める慣習を守り、フィールドは持てない点とメンバーはpublicである点を押さえましょう。

公開後のメンバー追加は影響が大きいため慎重に行い、役割ごとに小さく分ける設計でポリモーフィズムを活かすのがコツです。

まずはIPlayableのような小さな契約から始め、実装の差し替えを体験してみてください。

この記事を書いた人
エーテリア編集部
エーテリア編集部

C#の入門記事を中心に、開発環境の準備からオブジェクト指向の基本まで、順を追って解説しています。ゲーム開発や業務アプリを目指す人にも役立ちます。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!