web-dev-qa-db-ja.com

一般的に制約されたタイプに対してインターフェイスを使用する理由は何ですか

ジェネリック型パラメーター(クラステンプレート、パラメトリックポリモーフィズムとも呼ばれますが、それぞれの名前には異なる意味が含まれます)をサポートするオブジェクト指向言語では、多くの場合、型パラメーターに型制約を指定して、それを降順にすることができます別のタイプから。たとえば、これはC#の構文です。

_//for classes:
class ExampleClass<T> where T : I1 {

}
//for methods:
S ExampleMethod<S>(S value) where S : I2 {
        ...
}
_

それらのインターフェースによって制約されるタイプよりも実際のインターフェースタイプを使用する理由は何ですか?たとえば、メソッドシグネチャI2 ExampleMethod(I2 value)を作成する理由は何ですか?

15
GregRos

パラメトリックバージョンを使用すると、

  1. 関数のユーザーへの詳細情報
  2. 作成できるプログラムの数を制限する(無料のバグチェック)

ランダムな例として、2次方程式の根を計算するメソッドがあるとします。

_int solve(int a, int b, int c) {
  // My 7th grade math teacher is laughing somewhere
}
_

そして、あなたはint以外のもののような他の種類の数でそれを働かせたいです。あなたは次のようなものを書くことができます

_Num solve(Num a, Num b, Num c){
  ...
}
_

問題は、これがあなたがそれを望んでいることを言っていないことです。それは言う

数字のような3つのもの(同じ方法である必要はない)を教えてください。いくつかの種類の数字をお返しします

ab、およびcintsである場合、それがわからないため、int sol = solve(a, b, c)のようなことはできません。メソッドは最後にintを返します!これは、より大きな表現でソリューションを使用したい場合、ダウンキャスティングと祈りを伴う厄介なダンスにつながります。

関数内では、誰かが浮動小数点数、bigint値、および度数を手渡す可能性があり、それらを加算して乗算する必要があります。これらの3つのクラス間の操作は意味不明なものになるため、これを静的に拒否します。学位はmod 360であるため、a.plus(b) = b.plus(a)および同様の陽気さが発生することはありません。

サブタイプ化でパラメトリック多態性を使用する場合、タイプは実際に私たちが意味することを示しているため、これらすべてを除外できます。

_<T : Num> T solve(T a, T b, T c)
_

または「数値である型を教えてくれれば、それらの係数を使って方程式を解くことができる」という言葉です。

これは他の多くの場所でも発生します。例のもう1つの良い情報源は、ある種のコンテナーを抽象化する関数、ala reversesortmapなどです。

21
Daniel Gratzer

それらのインターフェースによって制約されるタイプよりも実際のインターフェースタイプを使用する理由は何ですか?

それがあなたが必要とするものだからです...

IFoo Fn(IFoo x);
T Fn<T>(T x) where T: IFoo;

2つの明らかに異なる署名です。最初は、インターフェイスを実装するany型を取り、それが行う唯一の保証は、戻り値がインターフェイスを満たすことです。

2番目は、インターフェイスを実装するanyタイプを受け取り、それが少なくともそのタイプを返すことを保証します(より制限の少ないインターフェイスを満たすものではなく)。

時には、より弱い保証が必要な場合があります。時にはあなたはより強いものを望みます。

16
Telastyn

メソッドパラメータに制約付きジェネリックを使用すると、渡されたものの戻り型に基づいて、メソッドの戻り値の型を非常に大きくすることができます。その中で:

  1. 制約付きジェネリックをrefまたはoutパラメータとして受け入れるメソッドには、制約を満たす変数を渡すことができます。対照的に、interface-typeパラメーターを持つ非ジェネリックメソッドは、その正確なインターフェースタイプの変数の受け入れに限定されます。

  2. ジェネリック型パラメーターTを持つメソッドは、Tのジェネリックコレクションを受け入れることができます。IList<T> where T:IAnimalを受け入れるメソッドはList<SiameseCat>を受け入れることができますが、IList<Animal>を必要とするメソッドはそうすることができます。

  3. 制約は、ジェネリック型の観点からインターフェースを指定することがあります。 where T:IComparable<T>

  4. インターフェースを実装する構造体は、制約のあるジェネリックパラメーターを受け入れるメソッドに渡されると値の型として保持されますが、インターフェース型として渡される場合はボックス化する必要があります。これは速度に大きな影響を与える可能性があります。

  5. ジェネリックパラメーターには複数の制約を設定できますが、「IFooとIBarの両方を実装するタイプ」のパラメーターを指定する方法は他にありません。タイプIFooのパラメーターを受け取ったコードは、問題のインスタンスであっても、二重制約ジェネリックを想定しているメソッドに渡すのが非常に難しいため、これは両刃の剣になることがあります。すべての制約を満たします。

特定の状況でジェネリックを使用する利点がない場合は、インターフェースタイプのパラメーターを受け入れます。ジェネリックを使用すると、型システムとJITterに余計な作業を強いることになります。そのため、メリットがない場合は、行うべきではありません。一方、上記の利点の少なくとも1つが適用されることは非常に一般的です。

2
supercat