インターフェイス(interface)を理解しよう!

[C#] インターフェイス(interface)を理解しよう!

※ 当サイトは広告を含みます。

ここではC#のインターフェイスについて学びます。
これは継承や抽象クラスと似た仕組みで、殆ど同種の機能と思っていいです。

インターフェイス(interface)

インターフェイスとは、あるクラスに機能の実装(メソッドやプロパティ等)を強制させる仕組みです。

◆ C#を問わないインターフェイスの意味はこちら。

今回の話なら対象はクラスになります。例としてClassAに足し算を依頼する話にしましょう。
ClassAに2個の数値を与えて足し算をして欲しいのですが、そもそもClassAは足し算ができるのでしょうか?

管理人
管理人

先に答えを言うと無理です。何故ってClassAは足し算を知らないから。

りさ
りさ

足し算すら出来ないとは無能。

管理人
管理人

そういうこと言わない。

僕たちが足し算を使えるのは、足し算という概念や仕組みを理解してるからです。
それを知らない人は足し算ができません。極端に言えば足し算も立派な数学的ルールです。

では、どうするか。結論は出てますが、足し算を教えればいいです。
この教える = 実装を強制するって意味になり、ClassAは足し算を理解できます。

次に構文とサンプルコードの確認です。とりあえず、先程の話をプログラム化してみます。

インターフェイス

namespace Sample
{
  // 足し算インターフェイス
  internal interface IAdditionable
  {
    // メソッドの宣言
    public int Addition(int x, int y);
  }

  internal class ClassA : IAdditionable
  {
    // インターフェイスを実装する
    public int Addition(int x, int y)
    {
      return x + y;
    }
  }

  internal class Program
  {
    static void Main(string[] args)
    {
      ClassA obj = new ClassA();
      int n = obj.Addition(2, 5);

      System.Console.WriteLine(n);
    }
  }
}
管理人
管理人

interfaceの名前は何でもいいのですが、接頭辞として'I'を付ける習慣があります。
ぶっちゃけ付いてないと無法者扱いされるので、何も考えず付けましょう。

りさ
りさ

後ろにableって付けるのも習慣ですか?

管理人
管理人

そっちは場合による。インターフェイスを実装すると、それが可能になるじゃん。
そのことを表現して〇〇ableって名前を付けることが結構あるんだ。

さて、先程の例ですがインターフェイスは実装を強制すると言いました。
よって以下はビルドエラーとなり実行できません。必ず機能を実装する必要があります。


namespace Sample
{
  // 足し算インターフェイス
  internal interface IAdditionable
  {
    // メソッドの宣言
    public int Addition(int x, int y);
  }

  internal class ClassA : IAdditionable
  {
    // 実装しないとビルドエラー
  }

  internal class Program
  {
    static void Main(string[] args)
    {
      ClassA obj = new ClassA();
      int n = obj.Addition(2, 5);

      System.Console.WriteLine(n);
    }
  }
}

ちなみにインターフェイスはクラスと異なりインスタンス化(実態)できません。
代わりに型宣言はできます。これは抽象クラスにかなり近い仕組みと思ってください。


namespace Sample
{
  internal interface IAdditionable
  {
    public int Addition(int x, int y);
  }

  internal class ClassA : IAdditionable
  {
    public int Addition(int x, int y)
    {
      return x + y;
    }
  }

  internal class Program
  {
    static void Main(string[] args)
    {
      // インスタンス化できないからビルドエラー
      //IAdditionable additionable = new IAdditionable();

      // インターフェイスを実装したクラスをインスタンス化して代入することは可能
      IAdditionable obj = new ClassA();
      int n = obj.Addition(2, 5);

      System.Console.WriteLine(n);
    }
  }
}

インターフェイスの目的

先程のパターンを学び「これメソッドで良くない?」って疑問を感じた人もいるでしょう。
それは半分(適当)くらい合ってます。当然ですがClassAだけでメソッド実装しても動きます。
もしくは既に学習した抽象クラスの仕組みを使っても同じようなことができます。

では、インターフェイスの存在理由とは何でしょうか?

1番大きいのは多重継承の問題があるからです。そもそもC#では多重継承できません。
そうなると同じような機能を実装する時に、何でもかんでも上位のクラスに放り込むことになります。

少し具体例を使って話しましょう。

四則演算を分割して実装することを考えます。機能は足し算、引き算、掛け算、割り算の4つです。
さらに四則演算を実装したいクラスが3つあるとします。これらは4つの演算から好きな種類の演算だけ実装できるものとします。

ここでは仮に以下のパターンにしましょう。

Class1 足し算, 引き算
Class2 足し算, 掛け算
Class3 引き算, 掛け算, 割り算

これを継承関係で作る場合、最上位の基底クラスに四則演算を全部実装して、それを継承したクラスを3つ作れば望みは叶います。
しかし、それは本当に正しいのでしょうか? 例えばClass1は足し算と引き算しか必要としません。にも関わらず全て実装されます。

ここでは四則演算を例にしてますが、それなりの機能を実装する場合には無駄と思いませんか?
そもそもクラスとは、機能を小さく分割して範囲を狭くすることに意味があります。なので、無駄な実装は省くべきなんです。

そう考えた場合の正解は、四則演算を4つ別々に実装して、それを継承することですが、それは多重継承となりC#では不可能です。
そんな時に利用するのがインターフェイスなんです。以下は先程のパターンのサンプルコードです。


namespace Sample
{
  // 足し算インターフェイス
  internal interface IAdditionable
  {
    public int Addition(int x, int y);
  }

  // 引き算インターフェイス
  internal interface ISubtractionable
  {
    public int Subtraction(int x, int y);
  }

  // 掛け算インターフェイス
  internal interface IMultiplicationable
  {
    public int Multiplication(int x, int y);
  }

  // 割り算インターフェイス
  internal interface IDivisionable
  {
    public int Division(int x, int y);
  }

  // 足し算、引き算を実装したクラス
  internal class Class1 : IAdditionable, ISubtractionable
  {
    public int Addition(int x, int y)
    {
      return x + y;
    }

    public int Subtraction(int x, int y)
    {
      return x - y;
    }
  }

  // 足し算、掛け算を実装したクラス
  internal class Class2 : IAdditionable, IMultiplicationable
  {
    public int Addition(int x, int y)
    {
      return x + y;
    }

    public int Multiplication(int x, int y)
    {
      return x * y;
    }
  }

  // 引き算、掛け算、割り算を実装したクラス
  internal class Class3 : ISubtractionable, IMultiplicationable, IDivisionable
  {
    public int Subtraction(int x, int y)
    {
      return x - y;
    }

    public int Multiplication(int x, int y)
    {
      return x * y;
    }

    public int Division(int x, int y)
    {
      return x / y;
    }
  }

  internal class Program
  {
    static void Main(string[] args)
    {
      Class1 c1 = new Class1();
      Class2 c2 = new Class2();
      Class3 c3 = new Class3();

      System.Console.WriteLine(c1.Addition(2, 4));
      System.Console.WriteLine(c1.Subtraction(2, 4));

      System.Console.WriteLine(c2.Addition(3, 5));
      System.Console.WriteLine(c2.Multiplication(3, 5));

      System.Console.WriteLine(c3.Subtraction(10, 2));
      System.Console.WriteLine(c3.Multiplication(10, 2));
      System.Console.WriteLine(c3.Division(10, 2));
    }
  }
}
管理人
管理人

このように1つのクラスで複数のインターフェイスを継承できます。

りさ
りさ

逆に同じような実装が被って無駄に思います。

管理人
管理人

確かにそうなんだけど、これにはメリットもデメリットもあるよ。

もっと複雑にならないと実感は得られないと思いますが、実装を継承先に委ねることはメリットもあります。
先程の例の延長で考えてみましょう。掛け算を*演算子で記述したけど、これは足し算でも実現可能です。


public int Multiplication(int x, int y)
{
  int n = 0;
  for (int i = 0; i < y; i++)
    n += x;

  return n;
}

ここで大切なのは、インターフェイスを利用する側からすれば内部の実装なんてどうでもいいんです。
極論、目的である2個の数値が掛け算された正しい結果が返れば中身を気にする必要はありません。
話を人間に置き換えれば、筆算、暗算、電卓など、答えが正しいならどの選択肢を選んでも構いません。

さらに伝えると、実装部分が被ってるなら、その部分をクラスやStatic系のメソッドにして外部に委ねることもできます。
この辺は設計の話なので深くは触れませんが、いくらでも解決策が存在するので問題にはなりません。

インターフェイスの継承

インターフェイスを継承したインターフェイスを作ることも可能です。
その場合はクラスになった時点で宣言されたインターフェイスの全ての実装が必要になります。


namespace Sample
{
  internal interface IGetValue
  {
    public int GetValue();
  }

  // インターフェイスの継承
  internal interface IGetValueEx : IGetValue
  {
    public int GetValueEx();
  }

  // 全てのインターフェイスを実装する必要がある
  internal class Class1 : IGetValueEx
  {
    public int GetValue()
    {
      return 1;
    }

    public int GetValueEx()
    {
      return 10;
    }
  }

  internal class Program
  {
    static void Main(string[] args)
    {
      Class1 c1 = new Class1();

      System.Console.WriteLine(c1.GetValue());
      System.Console.WriteLine(c1.GetValueEx());
    }
  }
}

インターフェイスの制約

クラスと違ってインターフェイスには色々と制約があります。

その中でも大きいのが実装を持てないことでしょう。インターフェイスの場合は基底インターフェイスに処理を書くことができません。
また、実装を持てないのでフィールドも作れません。あくまで宣言の集合がインターフェイスです。

と言うのは遥か過去、C#の8.0(らしい)で制約が緩和され、インターフェイスでもメソッドやプロパティの実装が書けるようになりました。
ただし、未だにフィールドは持てません。さらに言うと、インターフェイスにのみ実装したメソッド等は、インターフェイスの型でないと呼び出せません。


namespace Sample
{
  internal interface ITest
  {
    public void Output()
    {
      System.Console.WriteLine("Interfaceに実装されたメソッドだよ。");
    }
  }

  internal class Test : ITest
  {
    // 何も実装しない
  }

  internal class Program
  {
    static void Main(string[] args)
    {
      // 呼び出せないパターン
      Test obj1 = new Test();
      //obj1.Output(); // ビルドエラー

      // 呼び出せるパターン
      ITest obj2 = new Test();
      obj2.Output();
    }
  }
}

これはインタフェースを継承したクラスで、ちゃんと中身を実装すれば解決できます。


namespace Sample
{
  internal interface ITest
  {
    public void Output()
    {
      System.Console.WriteLine("Interfaceに実装されたメソッドだよ。");
    }
  }

  internal class Test : ITest
  {
    public void Output()
    {
      System.Console.WriteLine("Testに実装されたメソッドだよ。");
    }
  }

  internal class Program
  {
    static void Main(string[] args)
    {
      Test obj1 = new Test();
      obj1.Output();
    }
  }
}
管理人
管理人

正直、僕はインターフェイスに実装書かない派だから知らん。

あとがき

実装済みインターフェイスを大量に継承したら、それ多重継承と変わらなくない? って疑問ある。

◆ C#に関する学習コンテンツ

この記事は参考になりましたか?

関連記事

コメント

この記事へのコメントはありません。