インタフェイスのプロパティを使いたい場面


初めに

C#のインタフェイスには、プロパティを定義できるというユニークな機能がある1
メソッドを定義するのは分かるけど、この機能はいったい何の得があるのか、一つだけ例を見つけたので共有する。

ASP.NET MVCのページング用プロパティ

検索結果一覧につきもののページングは、汎用性が高いパーツの一部である。複雑なシステムの場合、多数のページでページングが必要になる。だからと言って、それぞれのページ用にページングを実装するのはコストがかかる。
そこで考えられるのがページング専用のクラスだが、これは面倒な多重継承問題があるので使いたくない。

つまりインタフェイスの出番である。インタフェイスで必要な値を渡せれば、クラスのように扱うことが可能だ。

サンプル.CS
public interface IPager
{
    int PageNo { get; set; }
    int MaxPages { get; set; }
}

public class HogeViewModel : IPager
{
    // モデルで使う(リストなど)プロパティは省略
    public int PageNo { get; set; }
    public int MaxPages { get; set; }
    // インタフェイスなので実装が必要
}

このようにすれば、ページャ生成用のパーシャルビューやヘルパに必要なプロパティのみを渡すことが可能になる。

ジェネリック型のファクトリメソッド

上を一般化した、というよりこちらがインターフェイスの本領だと思われる。
例えば、以下の様なクラスを定義したとする。

型.cs
public class Base
{
    public int Foo { get; set; }
}

public class Derived: Base
{
    public double Bar { get; set; }
}

このBase型を戻り値にしたメソッドを作成する。

メソッド.cs
public static Base Create(int i)
{
    return new Base{ Foo = i };
}

static void Main(string[] args)
{
    Derived baz = Create(1); // ダウンキャストができないのでエラー
}

C#はダウンキャストが基本的にできないのでこのような書き方は不可能(実用的には割と使うパターンなのに!)。
これをインタフェイスに変えてみても、インタフェイスの実装がBaseなのでDerivedに変換不能である。

インタフェイス実装.cs
public Interface IBase
{
    int Foo { get; set; }
}

public class Base: IBase
{
    public int Foo { get; set; }
}

public class Derived: IBase
{
    public int Foo { get; set; }
    public double Bar { get; set; }
}

public static IBase Create(int i)
{
    return new Base{ Foo = i };
}

static void Main(string[] args)
{
    Derived baz = Create(1); // Create戻り値の中身はBaseなのでDerivedにできない。エラー
}

このようなときはCreateをジェネリック化する。

メソッド.cs
public static T Create<T>(int i): where T: IBase, new()
{
    return new T{ Foo = i };
}

static void Main(string[] args)
{
    Derived baz = Create<Derived>(1); // ジェネリックメソッドに型情報を教える必要がある
}

この規模ならDerivedがBaseを直接継承して、Tの型制約をBaseにした方が楽2ではあるが、インタフェイスは複数継承できるので、大規模開発ならインタフェイスを使う意味があるかもしれない。

まとめ

上記で示した通り、使用頻度が高いプロパティはインタフェイスにまとめると、コード全体の汎用性が増す。
また、例えばページング用プロパティの他にも検索条件用のインタフェイスも実装できるので、型の制約に捉われないメソッドを使用できるようになる。

欲を言えば、Visual Studio(2015です)でインタフェイス実装宣言をした際に自動でプロパティを生成できるといいなと思った。クイック操作で追加されるプロパティはNotImplementedException付きなので使い辛い。自動実装プロパティにしてほしい。

更新履歴

2021/12/01 「ジェネリック型のファクトリメソッド」項追加。


  1. Javaの場合、プロパティはアクセサをメソッドで実装することになるから、ある意味Javaにも存在する機能ではある。他の言語はよく知らないので、そこまでユニークというわけでもないかも。 

  2. その場合でも、Baseを継承する方全てがパブリックのコンストラクタを持っているとは限らないので、new()制約は外せない。