private readonly
LinkLabel
sのリスト(IList<LinkLabel>
)。後でこのリストにLinkLabel
sを追加し、次のようにそれらのラベルをFlowLayoutPanel
に追加します。
foreach(var s in strings)
{
_list.Add(new LinkLabel{Text=s});
}
flPanel.Controls.AddRange(_list.ToArray());
Resharperは警告を表示します:Co-variant array conversion from LinkLabel[] to Control[] can cause run-time exception on write operation
。
私が理解するのを助けてください:
これが意味するのはこれ
Control[] controls = new LinkLabel[10]; // compile time legal
controls[0] = new TextBox(); // compile time legal, runtime exception
そして、より一般的な用語で
string[] array = new string[10];
object[] objs = array; // legal at compile time
objs[0] = new Foo(); // again legal, with runtime exception
C#では、オブジェクトの配列(この場合はLinkLabels)を基本型の配列(この場合はコントロールの配列)として参照できます。また、配列にControl
であるanotherオブジェクトを割り当てることはコンパイル時に有効です。問題は、配列実際にはControlsの配列ではない実行時、それは依然としてLinkLabelsの配列であるということです。そのため、割り当てまたは書き込みは例外をスローします。
アンソニーペグラムの答えを明確にしようとします。
ジェネリック型は、その型の値を返すときに、ある型引数に対して共変です(たとえば、Func<out TResult>
はTResult
のインスタンスを返し、IEnumerable<out T>
はT
のインスタンスを返します)。つまり、何かがTDerived
のインスタンスを返す場合、そのようなインスタンスをTBase
であるかのように扱うこともできます。
ジェネリック型は、その型の値を受け入れる場合、一部の型引数に対して反変です(たとえば、Action<in TArgument>
はTArgument
のインスタンスを受け入れます)。つまり、何かがTBase
のインスタンスを必要とする場合、TDerived
のインスタンスを渡すこともできます。
何らかのタイプのインスタンスを受け入れ、返すジェネリックタイプ(ジェネリックタイプシグネチャ、たとえばCoolList<TIn, TOut>
で2回定義されていない限り)は、対応するタイプ引数で共変でも反変でもないことは非常に論理的なようです。たとえば、List
は、.NET 4ではList<T>
やList<in T>
ではなく、List<out T>
として定義されています。
いくつかの互換性の理由により、Microsoftはその引数を無視し、値型引数で配列を共変にしました。おそらく分析を行った結果、ほとんどの人は配列を読み取り専用であるかのようにしか使用せず(つまり、配列初期化子を使用して一部のデータを配列に書き込むだけであるため)、実行可能性に起因する欠点を上回る利点があることがわかりました誰かが配列に書き込むときに共分散を利用しようとするとエラーが発生します。したがって、許可されていますが、推奨されていません。
元の質問に関しては、list.ToArray()
は元のリストからコピーされた値で新しいLinkLabel[]
を作成します。(合理的な)警告を取り除くには、Control[]
をAddRange
に渡す必要があります。 。 list.ToArray<Control>()
は仕事をします:ToArray<TSource>
は引数としてIEnumerable<TSource>
を受け入れ、TSource[]
を返します。 List<LinkLabel>
は、読み取り専用のIEnumerable<out LinkLabel>
を実装します。これは、IEnumerable
共分散のおかげで、IEnumerable<Control>
を引数として受け入れるメソッドに渡すことができます。
最も単純な「解決策」
flPanel.Controls.AddRange(_list.AsEnumerable());
List<LinkLabel>
をIEnumerable<Control>
に共変的に変更しているため、アイテムを列挙可能に「追加」することはできないため、これ以上の心配はありません。
警告は、LinkLabel[]
参照を介してControl[]
にControl
以外のLinkLabel
を理論的に追加できるという事実によるものです。これにより、ランタイム例外が発生します。
AddRange
はControl[]
を取るため、変換はここで行われます。
より一般的には、派生型のコンテナーを基本型のコンテナーに変換することは、後で概説した方法でコンテナーを後で変更できない場合にのみ安全です。配列はその要件を満たしていません。
問題の根本原因は他の回答に正しく記載されていますが、警告を解決するために、いつでも記述できます。
_list.ForEach(lnkLbl => flPanel.Controls.Add(lnkLbl));
VS 2008では、この警告は表示されません。これは.NET 4.0の新しいものでなければなりません。
明確化:Sam Mackrillによると、警告を表示するのはResharperです
C#コンパイラは、AddRange
が渡された配列を変更しないことを知りません。 AddRange
にはControl[]
型のパラメーターがあるため、理論的にはTextBox
を配列に割り当てようとすることができます。これは、Control
が、配列は実際にはLinkLabels
の配列であり、そのような割り当てを受け入れません。
C#で配列を共変にすることは、Microsoftの悪い決断でした。そもそも派生型の配列を基本型の配列に割り当てることができるのは良い考えかもしれませんが、これは実行時エラーにつながる可能性があります!
これはどう?
flPanel.Controls.AddRange(_list.OfType<Control>().ToArray());