インターフェイスの明示的実装の用途

もう少し具体的に、インターフェイスの明示的実装の用途をいくつか紹介しましょう。

インターフェイスの明示的実装は、同じ名前のメンバーを持ったインターフェイスを複数同時に実装できるようにするための機能です。 では、それが必要になる場面というのは具体的にはどういう状況でしょう。 また、メンバーをいったんインターフェイス型にキャストしてからでないと呼べなくなるという性質も、有効に使える場面があります。

消したいけど消せないメソッドを隠す

まず一般論として、public なものは、足すより消す方が難しいです。他人の作ったライブラリを使っていて、ある日突然、自分の使っているメソッドが消えたらどうでしょう。自分は何もしていないのに、自分の書いたコードがコンパイルできなくなります。

この問題はライブラリが広く使われれば使われるほど影響範囲が広がります。標準ライブラリに至っては、まず削除はできないものだと思ってください。

その結果、.NETの標準ライブラリには、いくつか、消したくても消せないものがあります。代表例として、以下のようなものがあります。

  • 非ジェネリック版のIEnumerableインターフェイス(System.Collections名前空間)
    • ジェネリック版のIEnumerable<T>(System.Collections.Generic名前空間)が、この非ジェネリック版から派生している
  • ICollection<T>インターフェイス(System.Collections.Generic名前空間)のIsReadOnly

これらを「消したい」理由については後で補足しますが、とりあえず、消したくても消してはいけません。

これらのインターフェイスを実装する際、その消したいけど消せないメソッドも一緒に実装させられるという苦行が待っています。 せめて、そんなもうあまり使わなくなったメンバーはpublicにしたくないわけです。 そこで、明示的実装の、メンバーを隠せる性質が使えます。

例としてIEnumerableインターフェイスを隠す方法を示しましょう。というか、すでに前述の例で使っていたりします。再掲すると以下の通りです。

using System.Collections;
using System.Collections.Generic;

class LinkedList<T> : IEnumerable<T>
{
    public T Value { get; }
    public LinkedList<T> Next { get; }

    public LinkedList(T value) : this(value, null) { }
    private LinkedList(T value, LinkedList<T> next) { Value = value; Next = next; }

    public LinkedList<T> Add(T value) => new LinkedList<T>(value, this);

    public IEnumerator<T> GetEnumerator()
    {
        if(Next != null)
            foreach (var x in Next)
                yield return x;
        yield return Value;
    }

    // 明示的実装。こいつは、IEnumerableを介さない限り見えなくなる
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

補足1: 非ジェネリック インターフェイス

特に、ジェネリック関連に多いです。 ジェネリックが.NET 1.0には間に合わず、2.0からの追加だったので、多くのインターフェイスで非ジェネリック版と、ジェネリック版が2重保守されています。

IEnumerableもその例の1つで、.NET 1.0時代に非ジェネリック版が、2.0でジェネリック版が入りました。2.0で入ったジェネリック版は、1.0時代のコードとの互換性のために非ジェネリック版から派生しています。もし、最初から.NETにジェネリックがあれば、非ジェネリック版の機能は不要でした。

補足2: IsReadOnly

インターフェイスが増えるというのはそれなりのコストがかかるそうで、.NETリリース初期の頃は、インターフェイスを減らす方向で設計を進めたそうです。ICollection<T>インターフェイスがIsReadOnlyというプロパティを持っているのはその頃の名残です。しかし今となっては、インターフェイスが増えてもいいからちゃんと「読み取り専用なコレクション」と「書き換え可能なコレクション」は別インターフェイスに分けるべきだということになっています(そのため、.NET 4.5で、IReadOnlyCollection<T>インターフェイスが(System.Collections.Generic名前空間)が追加されました)。

つまり、今と昔で以下のような思想の差があります。

  • 昔: インターフェイスを増やしたくないので、コレクションが読み取り専用か書き換え可能かはプロパティで返していた
  • 今: 読み取り専用ならIReadOnlyCollection<T>インターフェイスを、書き換え可能ならICollection<T>インターフェイスを使う

こうなると、IsReadOnlyプロパティははっきり言って邪魔です。ICollection<T>を選んだ時点で書き換え可能にしたいんだから、おそらくは常にtrueを返すだけになるでしょう。

メンバーのアクセスを制限する

(書きかけ)

  • internal set 隠し
  • internal interface 実装できるのとの組み合わせ

ジェネリック版とobject版

(書きかけ)

ときどき、「特定のインターフェイスを実装している時だけ特別な動作を挟む」みたいな処理を書きたい場合があります。

  • この as 判定用に interface IX { object X { get; } }
  • でも、人手で使うとき用にジェネリック版を用意して interface IX<T> : IX { new T X { get; } }

更新履歴

ブログ