プログラミング言語の共分散と違反
-
18-09-2019 - |
質問
プログラミング言語理論における共分散と違反の概念、誰も私を説明できますか?
解決
共分散 いくつかのコレクションクラスの観点からは非常にシンプルで最高の考えです List
. 。私たちはできる パラメーター化 List
いくつかのタイプパラメーターを持つクラス T
. 。つまり、私たちのリストにはタイプの要素が含まれています T
いくつかのための T
. 。リストは共変動の場合です
Sはt iffリストのサブタイプです[s]はリストのサブタイプです[t
(数学的定義を使用しています iff 意味する 場合にのみ.)
あれは List[Apple]
aです List[Fruit]
. 。 aを受け入れるルーチンがある場合 List[Fruit]
パラメーターとして、そして私はaを持っています List[Apple]
, 、次に、これを有効なパラメーターとして渡すことができます。
def something(l: List[Fruit]) {
l.add(new Pear())
}
コレクションクラスの場合 List
可変性は、私たちのルーチンが上記のように他の果物(リンゴではなかった)を追加できると仮定するかもしれないので、共分散は意味がありません。したがって、私たちは好きなだけです 不変 コバリエントになるコレクションクラス!
他のヒント
C#4.0に新しい分散機能を追加した方法に関する私の記事を以下に示します。下から始めます。
http://blogs.msdn.com/ericlippert/archive/tags/covariance++contravariance/default.aspx
利便性を高めるために、エリックリッパートのすべての分散記事へのリンクの注文されたリストを次に示します。
その間に区別があります 共分散 と 違反.
非常に大まかに、操作がタイプの順序を保持する場合、操作は共変と、それが違反します 反転 この注文。
秩序自体は、より具体的なタイプよりも大きいものとして、より一般的なタイプを表すことを目的としています。
C#が共分散をサポートする状況の一例を次に示します。まず、これはオブジェクトの配列です。
object[] objects=new object[3];
objects[0]=new object();
objects[1]="Just a string";
objects[2]=10;
もちろん、アレイに異なる値を挿入することは可能です。 System.Object
.NETフレームワーク。言い換えると、 System.Object
非常に一般的です 大きい タイプ。ここに、共分散がサポートされている場所があります。
より小さなタイプの値をより大きなタイプの変数に割り当てる
string[] strings=new string[] { "one", "two", "three" };
objects=strings;
タイプの可変オブジェクト object[]
, 、実際にタイプの値を保存できます string[]
.
考えてみてください - 点まで、それはあなたが期待するものですが、それからそうではありません。結局のところ、その間 string
から派生 object
, string[]
ではない から派生します object[]
. 。この例での共分散の言語サポートにより、とにかく割り当てが可能になります。これは、多くの場合に見られるものです。 分散 言語をより直感的に機能させる機能です。
これらのトピックに関する考慮事項は非常に複雑です。たとえば、前のコードに基づいて、エラーをもたらす2つのシナリオを次に示します。
// Runtime exception here - the array is still of type string[],
// ints can't be inserted
objects[2]=10;
// Compiler error here - covariance support in this scenario only
// covers reference types, and int is a value type
int[] ints=new int[] { 1, 2, 3 };
objects=ints;
違反の仕組みの例は、もう少し複雑です。これらの2つのクラスを想像してください:
public partial class Person: IPerson {
public Person() {
}
}
public partial class Woman: Person {
public Woman() {
}
}
Woman
から派生しています Person
, 、 明らかに。ここで、これらの2つの関数があると考えてください。
static void WorkWithPerson(Person person) {
}
static void WorkWithWoman(Woman woman) {
}
関数の1つは、 Woman
, 、もう1つはより一般的で、派生したあらゆるタイプで動作することができます Person
. 。に Woman
物事の側面、あなたは今もこれらを持っています:
delegate void AcceptWomanDelegate(Woman person);
static void DoWork(Woman woman, AcceptWomanDelegate acceptWoman) {
acceptWoman(woman);
}
DoWork
をとることができる関数です Woman
そして、もかかる関数への参照 Woman
, 、それからそれはのインスタンスを渡します Woman
代表者に。考えます 多型 ここにある要素の。 Person
は 大きい よりも Woman
, 、 と WorkWithPerson
は 大きい よりも WorkWithWoman
.
WorkWithPerson
考慮されます 大きい よりも AcceptWomanDelegate
分散の目的で。
最後に、これらの3行のコードがあります。
Woman woman=new Woman();
DoWork(woman, WorkWithWoman);
DoWork(woman, WorkWithPerson);
a Woman
インスタンスが作成されます。その後、ダウォークが呼ばれ、渡されます Woman
インスタンスとへの参照 WorkWithWoman
方法。後者は、明らかにデリゲートタイプと互換性があります AcceptWomanDelegate
- タイプの1つのパラメーター Woman
, 、リターンタイプなし。ただし、3行目は少し奇妙です。メソッド WorkWithPerson
を取る Person
パラメーターとして、ではありません Woman
, 、必要に応じて AcceptWomanDelegate
. 。それにもかかわらず、 WorkWithPerson
デリゲートタイプと互換性があります。 違反 それを可能にするので、委任の場合はより大きなタイプを WorkWithPerson
小さなタイプの変数に保存できます AcceptWomanDelegate
. 。もう一度それは直感的なことです: もしも WorkWithPerson
どんなものでも動作できます Person
, 、aを通過します Woman
間違ってはいけません, 、 右?
今では、これがすべてジェネリックにどのように関係しているのか疑問に思うかもしれません。答えは、分散もジェネリックにも適用できるということです。使用された前の例 object
と string
配列。ここで、コードは配列の代わりに汎用リストを使用します。
List<object> objectList=new List<object>();
List<string> stringList=new List<string>();
objectList=stringList;
これを試してみると、これはC#でサポートされているシナリオではないことがわかります。 C#バージョン4.0および.NETフレームワーク4.0では、ジェネリックの分散サポートがクリーンアップされており、新しいキーワードを使用できるようになりました。 の と アウト 汎用型パラメーター付き。特定のタイプパラメーターのデータフローの方向を定義および制限し、分散を機能させることができます。しかし、の場合 List<T>
, 、タイプのデータ T
両方向に流れる - タイプには方法があります List<T>
その戻り T
値、およびそのような価値を受け取る他の人。
これらの方向制限のポイントはです それが理にかなっている場所に分散を可能にするため, 、しかし 問題を防ぎます 以前の配列の例の1つで言及されているランタイムエラーのように。タイプパラメーターが正しく装飾されている場合 の また アウト, 、コンパイラは、その分散をチェックし、許可するか、許可することができます。 時間をまとめます. 。 Microsoftは、.NETフレームワークの多くの標準インターフェイスにこれらのキーワードを追加する努力に進んでいます。 IEnumerable<T>
:
public interface IEnumerable<out T>: IEnumerable {
// ...
}
このインターフェースでは、タイプのデータフロー T
オブジェクトは明確です: それらは、このインターフェイスによってサポートされている方法からしか取得できず、それらに渡されません. 。その結果、と同様の例を作成することが可能です List<T>
前述の試みですが、使用します IEnumerable<T>
:
IEnumerable<object> objectSequence=new List<object>();
IEnumerable<string> stringSequence=new List<string>();
objectSequence=stringSequence;
このコードは、バージョン4.0以降、C#コンパイラには受け入れられます。 IEnumerable<T>
のために共変動です アウト タイプパラメーターの指定器 T
.
ジェネリックタイプを使用する場合、分散と、コンパイラがさまざまな種類のトリックを適用して、コードを期待どおりに機能させる方法を認識することが重要です。
この章でカバーされている以上の分散について知ることがありますが、これにより、すべてのコードを理解できるようにするには十分です。
参照:
Bart de Smetには共分散と違反に関する素晴らしいブログエントリがあります ここ.
C#とCLRの両方により、メソッドを代表者に結合する際に、参照タイプの共分散と対照的なバリエンスが可能になります。共分散とは、メソッドがデリゲートのリターンタイプから派生したタイプを返すことができることを意味します。 Contra-Varianceとは、メソッドがデリゲートのパラメータータイプのベースであるパラメーターを取得できることを意味します。たとえば、次のように定義された代表者が与えられます。
委任オブジェクトmycallback(fileStream s);
プロトタイプ化されたメソッドにバインドされたこのデリゲートタイプのインスタンスを構築することができます
このような:
文字列somemethod(stream s);
ここで、SomeMethodのリターンタイプ(文字列)は、代表者のリターンタイプ(オブジェクト)から派生したタイプです。この共分散は許可されています。 SomeMethodのパラメータータイプ(ストリーム)は、デリゲートのパラメータータイプ(FileStream)のベースクラスであるタイプです。この逆の変動は許可されています。
共分散とコントラバリケーションは、値の種類やvoidではなく、参照タイプのみでサポートされていることに注意してください。したがって、たとえば、次の方法をMyCallback Delegateに結合することはできません。
int32 sotermethod(stream s);
someothermethodのリターンタイプ(int32)はmycallbackのリターンタイプ(オブジェクト)から派生していますが、INT32が値タイプであるため、この形式の共分散は許可されていません。
明らかに、共分散とコントラバリケーションに値のタイプとvoidを使用できない理由は、これらのもののメモリ構造が異なるため、参照タイプのメモリ構造は常にポインターです。幸いなことに、C#コンパイラは、サポートされていないことをしようとすると、エラーが発生します。