5

現在、同僚向けに C# の新しいジェネリック バリアンス機能のプレゼンテーションを準備しています。話を短くするために、次の行を書きました。

IList<Form> formsList = new List<Form> { new Form(), new Form() };
IList<Control> controlsList = formsList;

はい、これはもちろん不可能です。 IList(Of T) は不変です (少なくとも私の考えでは)。コンパイラは次のように教えてくれます。

System.Collections.Generic.IList<System.Windows.Forms.Form>タイプを に 暗黙的に変換することはできません System.Collections.Generic.IList<System.Windows.Forms.Control>。明示的な変換が存在します (キャストがありませんか?)

うーん、これは明示的な変換を強制できるということですか? 私はちょうどそれを試しました:

IList<Form> formsList = new List<Form> { new Form(), new Form() };
IList<Control> controlsList = (IList<Control>)formsList;

そして…コンパイル!不変性を捨てることができるということですか?- 少なくともコンパイラは問題ありませんが、以前のコンパイル時エラーを実行時エラーに変更しました:

Unable to cast object of type 'System.Collections.Generic.List`1[System.Windows.Forms.Form]' to type 'System.Collections.Generic.IList`1[System.Windows.Forms.Control]'.

私の質問: の不変性IList<T>(または私の実験に関する他の不変のインターフェイス) を捨てることができるのはなぜですか? 私は本当に不変性を捨てるのですか、それともここでどのような種類の変換が行われますか (IList(Of Form)IList(Of Control)は完全に無関係です)? これは私が知らなかった C# の暗いコーナーですか?

4

2 に答える 2

6

基本的に、型実装IList<Control> することも IList<Form>できるので、キャストが成功する可能性もあります-コンパイラは当分の間それを通過させます(ただし、参照される具体的な型を知っているため、ここではよりスマートになり、警告が生成される可能性がありますオブジェクトですが、そうではありません。新しいインターフェイスを実装するための型の重大な変更ではないため、コンパイラエラーを生成することは適切ではないと思います)。

そのようなタイプの例として:

public class EvilList : IList<Form>, IList<Control> { ... }

実行時に行われるのは、CLRの型チェックだけです。表示されている例外は、この操作の失敗を表しています。

キャスト用に生成されたILは次のとおりです。

castclass [mscorlib]System.Collections.Generic.IList`1<class [System.Windows.Forms]System.Windows.Forms.Control>

MSDNから:

castclass命令は、スタックの最上位にあるオブジェクト参照(タイプO)を指定されたクラスにキャストしようとします。新しいクラスは、目的のクラスを示すメタデータトークンによって指定されます。スタックの最上位にあるオブジェクトのクラスが新しいクラスを実装せず(新しいクラスがインターフェイスであると想定)、新しいクラスの派生クラスでない場合、InvalidCastExceptionがスローされます。オブジェクト参照がnull参照の場合、castclassは成功し、新しいオブジェクトをnull参照として返します。

objをクラスにキャストできない場合、InvalidCastExceptionがスローされます。

于 2011-10-26T18:49:46.303 に答える
1

この場合、新しい TextBlock を controlsList に追加しようとすると、実行時例外がスローされるのではないかと思います。TextBlock は controlsList のコントラクトに準拠しますが、formsList には準拠しません。

IList<Form> formsList = new List<Form> { new Form(), new Form() }; 
IList<Control> controlsList = (IList<Control>)formsList; 
controlsList.Add(New TextBlock); // Should throw at runtime.

この場合、通常、タイプ セーフな不変性は実行時例外として頭に浮かびます。この場合、IEnumerable は共変 (IEnumerable) として宣言されているため、controlsList を IList ではなく IEnumerable として宣言しても安全です (.Net 4.0 を想定)。これにより、out インターフェイスから .Add (およびその他の入力メソッド) を使用できないために、間違った型をコントロール リストに追加しようとする問題が解決されます。

于 2011-10-26T18:56:51.500 に答える