16

C# 4.0 の Generic Variance は、例外なく次のように記述できるように実装されています (これは C# 3.0 で発生することです)。

 List<int> intList = new List<int>();
 List<object> objectList = intList; 

[機能しない例: Jon Skeet の回答を参照]

私は最近、Jon Skeet が Generic Variance の優れた概要を説明した会議に出席しましたが、完全に理解できているかどうかはわかりません.contra と co-variance に関してはinoutキーワードの重要性を理解していますが、私は舞台裏で何が起こっているのか興味があります。

このコードが実行されると、CLR は何を認識しますか? を暗黙的に変換するのList<int>か、List<object>それとも派生型から親型に変換できるようになったのか、それとも単に組み込まれているだけなのか?

興味深いことに、これが以前のバージョンで導入されなかったのはなぜですか?主な利点は何ですか?つまり、実際の使用法ですか?

Generic Variance のこの投稿に関する詳細情報(ただし、質問は非常に時代遅れであり、実際の最新の情報を探しています)

4

3 に答える 3

20

いいえ、あなたの例は次の 3 つの理由で機能しません。

  • クラス ( などList<T>) は不変です。デリゲートとインターフェイスのみがバリアントです
  • 分散が機能するためには、インターフェイスは型パラメーターを一方向 (反分散の場合は in、共分散の場合は out) でのみ使用する必要があります。
  • 値型は分散の型引数としてサポートされていないため、たとえばからIEnumerable<int>への変換はありませんIEnumerable<object>

(コードは C# 3.0 と 4.0 の両方でコンパイルに失敗します - 例外はありません。)

したがって、これ機能します:

IEnumerable<string> strings = new List<string>();
IEnumerable<object> objects = strings;

CLR は参照を変更せずに使用するだけで、新しいオブジェクトは作成されません。したがって、電話objects.GetType()してもList<string>.

言語設計者がそれを公開する方法の詳細をまだ解決しなければならなかったため、以前に導入されなかったと思います.v2からCLRに含まれています。

利点は、あるタイプを別のタイプとして使用できるようにしたい場合と同じです。先週の土曜日に使用したのと同じ例を使用すると、領域ごとに形状を比較するための何かの実装がある場合、それIComparer<Shape>を使用して並べ替えができないのはおかしいです。List<Circle>円。C# 4 の時点で、 からIComparer<Shape>への反変変換があるIComparer<Circle>ため、 を呼び出すことができますcircles.Sort(areaComparer)

于 2010-02-05T15:04:55.213 に答える
15

いくつかの追加の考え。

このコードが実行されると、CLR は何を認識しますか

Jon と他の人が正しく指摘しているように、私たちはクラスではなく、インターフェイスとデリゲートのみを変更しています。したがって、あなたの例では、CLR は何も見えません。そのコードはコンパイルされません。十分なキャストを挿入して強制的にコンパイルすると、実行時に不正なキャスト例外でクラッシュします。

さて、バリアンスが実際に機能しているときに、バックグラウンドでバリアンスがどのように機能するかを尋ねることは、依然として妥当な質問です。答えは: インターフェイスとデリゲート型をパラメーター化する参照型引数にこれを制限する理由は、舞台裏で何も起こらないようにするためです。あなたが言う時

object x = "hello";

舞台裏で起こっているのは、文字列への参照が変更されずに object 型の変数にスタックされていることです。文字列への参照を構成するビットは、オブジェクトへの参照として有効なビットであるため、ここでは何も行う必要はありません。CLR は単純に、これらのビットを文字列を参照するものとして考えるのをやめ、オブジェクトを参照するものとして考え始めます。

あなたが言う時:

IEnumerator<string> e1 = whatever;
IEnumerator<object> e2 = e1;

同じこと。何も起こりません。文字列列挙子への参照を作成するビットは、オブジェクト列挙子への参照を作成するビットと同じです。キャストを行うと、もう少し魔法がかかります。たとえば、次のように言います。

IEnumerator<string> e1 = whatever;
IEnumerator<object> e2 = (IEnumerator<object>)(object)e1;

ここで、CLR は、e1 が実際にそのインターフェイスを実装しているかどうかのチェックを生成する必要があります。そのチェックは、差異を認識することについて賢くなければなりません。

しかし、バリアント インターフェイスがノーオペレーション変換であることを回避できる理由は、通常の割り当ての互換性がそのようになっているためです。e2は何に使うの?

object z = e2.Current;

これは、文字列への参照であるビットを返します。それらが変更なしで object と互換性があることは既に確立しています。

なぜこれが以前に導入されなかったのですか?他にもやるべき機能があり、予算も限られていました。

主なメリットは?文字列のシーケンスからオブジェクトのシーケンスへの変換は「うまくいく」。

于 2010-02-10T00:42:56.620 に答える
8

興味深いことに、これが以前のバージョンで導入されなかったのはなぜですか

.NET の最初のバージョン (1.x) にはジェネリックがまったくなかったので、ジェネリックの違いはまったくありませんでした。

.NET のすべてのバージョンで、配列の共分散があることに注意してください。残念ながら、それは危険な共分散です:

Apple[] apples = new [] { apple1, apple2 };
Fruit[] fruit = apples;
fruit[1] = new Orange(); // Oh snap! Runtime exception! Can't store an orange in an array of apples!

C# 4 の共分散と反分散は安全であり、この問題を防ぎます。

主な利点は何ですか?つまり、実際の使用法は?

多くの場合、コードで API を呼び出すときに Base の増幅型 (例: IEnumerable<Base>) が必要ですが、得られるのは Derived の増幅型 (例: IEnumerable<Derived>) だけです。

C# 2 および C# 3 では、IEnumerable<Base>「正常に機能する」はずですが、手動で に変換する必要があります。共分散と反分散により、「うまく機能する」ようになります。

psスキートの答えが私のすべての担当者ポイントを食べているのはまったく残念です。くそー、スキート!:-)しかし、彼は以前にこれに答えたようです。

于 2010-02-05T15:09:43.367 に答える