158

IEnumerable<T>共変ですが、値型はサポートせず、参照型のみをサポートします。以下の単純なコードは正常にコンパイルされます。

IEnumerable<string> strList = new List<string>();
IEnumerable<object> objList = strList;

しかし、 からstringに変更するintと、コンパイル エラーが発生します。

IEnumerable<int> intList = new List<int>();
IEnumerable<object> objList = intList;

理由はMSDNで説明されています:

差異は参照型にのみ適用されます。バリアント型パラメーターに値の型を指定すると、その型パラメーターは結果として構築される型に対して不変になります。

私が検索したところ、値の型と参照の型の間のボクシングが理由であると述べたいくつかの質問が見つかりました。しかし、なぜボクシングが理由なのか、まだよくわかりません。

共分散と反分散が値の型をサポートしない理由と、ボクシングがこれにどのように影響するかを簡単かつ詳細に説明してください。

4

4 に答える 4

130

基本的に、分散は、CLR が値に表現上の変更を加える必要がないことを保証できる場合に適用されます。参照はすべて同じように見えるため、表現を変更せずに をIEnumerable<string>として使用できます。IEnumerable<object>ネイティブ コード自体は、値が確実に有効であることをインフラストラクチャが保証している限り、値を使用して何を行っているかを知る必要はまったくありません。

値型の場合、それは機能しません。 anIEnumerable<int>を として扱うIEnumerable<object>には、シーケンスを使用するコードは、ボックス化変換を実行するかどうかを知る必要があります。

このトピックの一般的な詳細については、表現とアイデンティティに関するEric Lippert のブログ記事を参照してください。

編集: エリックのブログ投稿を自分で読み直したところ、少なくとも表現と同じくらいアイデンティティに関するものですが、2つはリンクされています. 特に:

これが、インターフェイス型とデリゲート型の共変変換と反変変換で、すべての可変型引数が参照型である必要がある理由です。バリアント参照変換が常に ID を保持するようにするには、型引数を含むすべての変換も ID を保持する必要があります。型引数のすべての重要な変換が同一性を維持するようにする最も簡単な方法は、それらを参照変換に制限することです。

于 2012-09-17T07:37:48.147 に答える
10

基礎となる表現について考えると、おそらく理解しやすいでしょう (これは実際には実装の詳細ですが)。文字列のコレクションは次のとおりです。

IEnumerable<string> strings = new[] { "A", "B", "C" };

stringsは次の表現を持つと考えることができます。

[0] : 文字列参照 -> "A"
[1] : 文字列参照 -> "B"
[2] : 文字列参照 -> "C"

これは 3 つの要素のコレクションで、それぞれが文字列への参照です。これをオブジェクトのコレクションにキャストできます。

IEnumerable<object> objects = (IEnumerable<object>) strings;

基本的には、参照がオブジェクト参照であることを除いて、同じ表現です。

[0] : オブジェクト参照 -> "A"
[1] : オブジェクト参照 -> "B"
[2] : オブジェクト参照 -> "C"

表現は同じです。参照の扱いが異なるだけです。string.Lengthプロパティにアクセスできなくなりましたが、 を呼び出すことはできますobject.GetHashCode()。これを int のコレクションと比較します。

IEnumerable<int> ints = new[] { 1, 2, 3 };
[0] : 整数 = 1
[1] : 整数 = 2
[2] : 整数 = 3

これを に変換するにはIEnumerable<object>、int をボックス化してデータを変換する必要があります。

[0] : オブジェクト参照 -> 1
[1] : オブジェクト参照 -> 2
[2] : オブジェクト参照 -> 3

この変換には、キャスト以上のものが必要です。

于 2012-09-17T07:51:17.017 に答える
8

LSPすべては(Liskov Substitution Principle)の定義から始まると思います。

q(x) が型 T のオブジェクト x について証明可能なプロパティである場合、q(y) は、S が T のサブタイプである型 S のオブジェクト y に対して真でなければなりません。

ただし、たとえば、値の型はinintの代わりにはなりません。証明は非常に簡単です:objectC#

int myInt = new int();
object obj1 = myInt ;
object obj2 = myInt ;
return ReferenceEquals(obj1, obj2);

これは、同じ「参照」をオブジェクトfalseに割り当てても返されます。

于 2012-09-17T07:40:25.183 に答える
3

実装の詳細に行き着きます。値型は、参照型とは異なる方法で実装されます。

値型を強制的に参照型として扱う (つまり、インターフェイスを介して参照するなどして値型をボックス化する) と、差異が発生する可能性があります。

違いを確認する最も簡単な方法は、単純に次のように考えることArrayです。Value 型の配列は連続して (直接) メモリ内にまとめられますが、Reference 型の配列は参照 (ポインタ) のみがメモリ内に連続して存在します。指しているオブジェクトは個別に割り当てられます。

もう 1 つの (関連する) 問題 (*) は、(ほぼ) すべての Reference 型が差異の目的で同じ表現を持ち、多くのコードが型間の違いを知る必要がないため、共分散と反分散が可能です (そして簡単に)実装されています -- 多くの場合、余分な型チェックを省略しただけです)。

(*) 同じ問題に見えるかも…

于 2012-09-17T10:02:42.220 に答える