xからyへの共変配列の変換によりランタイム例外が発生する可能性があります


142

私が持っているprivate readonlyのリストLinkLabel(複数可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

私が理解するのを助けてください:

  1. これはどういう意味ですか?
  2. これはユーザーコントロールであり、ラベルを設定するために複数のオブジェクトからアクセスされないため、コードをそのままにしても影響はありません。

回答:


154

これはどういう意味ですか

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)を基本型の配列(この場合は、コントロールの配列)として参照できます。配列にaである別のオブジェクトを割り当てることも、コンパイル時に有効Controlです。問題は、配列が実際にはコントロールの配列ではないことです。実行時には、それはまだLinkLabelsの配列です。そのため、割り当てまたは書き込みは例外をスローします。


あなたの例のようにランタイム/コンパイル時間の違いを理解していますが、特別な型から基本型への変換は合法ではありませんか?さらに、リストを入力して、LinkLabel(特殊タイプ)からControl(ベースタイプ)に移動します。
TheVillageIdiot

2
はい、LinkLabelからControlへの変換は合法ですが、これはここで行われていることと同じではありません。これは、LinkLabel[]からへの変換に関する警告ですControl[]。これは依然として有効ですが、実行時の問題が発生する可能性があります。変更されたのは、配列の参照方法だけです。配列自体は変更されません。問題を参照してください?配列は引き続き派生型の配列です。参照は、基本型の配列を介して行われます。したがって、基本型の要素に要素を割り当てることはコンパイル時に有効です。しかし、ランタイムタイプはそれをサポートしません。
Anthony Pegram

あなたの場合、私はそれが問題だとは思いません、あなたは単に配列を使ってコントロールのリストに追加しているだけです。
Anthony Pegram、2012年

6
なぜC#で配列が誤って共変であるのか疑問に思う人がいる場合は、Eric Lippertの説明です。Javaが必要とし、CLR設計者がJavaのような言語をサポートできるようにしたかったため、CLRに追加されました。その後、CLRにあったため、C#に追加しました。この決定は当時非常に物議を醸していましたが、私はそれについてあまり満足していませんが、現在、私たちがそれについてできることは何もありません。
franssu 2014年

14

Anthony Pegramの回答を明確にします。

それは言っ型の値(例えば返すときに一般的なタイプは、いくつかの型引数に共変でFunc<out TResult>の返品インスタンスTResultIEnumerable<out T>の戻りインスタンスをT)。つまり、何かがのインスタンスを返す場合、そのインスタンスをのようTDerivedに操作することもできますTBase

ジェネリック型は、その型の値をAction<in TArgument>受け入れる(たとえば、のインスタンスを受け入れるTArgument)ときに、いくつかの型引数に対して反変です。つまり、何かがのインスタンスを必要とする場合TBase、のインスタンスを渡すこともできますTDerived

あるタイプのインスタンスを受け入れて返す両方のジェネリック型(ジェネリック型シグネチャで2回定義されていない場合などCoolList<TIn, TOut>)は、対応する型引数で共変でも反変でもないことは非常に論理的です。例えば、Listとして.NET 4で定義されList<T>ていない、List<in T>またはList<out T>

いくつかの互換性の理由により、Microsoftはその引数を無視し、値の型の引数に対して配列を共変にする可能性があります。多分彼らは分析を行ったところ、ほとんどの人は読み取り専用であるかのように配列のみを使用している(つまり、配列初期化子のみを使用して配列にデータを書き込んでいる)ことがわかりました。誰かが配列に書き込むときに共分散を利用しようとするとエラーになります。したがって、許可されていますが推奨されていません。

あなたの元の質問については、list.ToArray()新しいを作成し、LinkLabel[]元のリストからコピーされた値で、かつ、(合理的)警告を取り除くために、あなたはに合格する必要がありますControl[]しますAddRangelist.ToArray<Control>()仕事をします:引数としてToArray<TSource>受け入れIEnumerable<TSource>て返しますTSource[]; List<LinkLabel>読み取り専用を実装しますIEnumerable<out LinkLabel>。これは、IEnumerable共分散によりIEnumerable<Control>、引数として受け入れるメソッドに渡すことができます。


11

最も簡単な「解決策」

flPanel.Controls.AddRange(_list.AsEnumerable());

列挙型にアイテムを「追加」することは不可能であるため、共変的に変更List<LinkLabel>IEnumerable<Control>ているので、これ以上の心配はありません。


10

警告は、理論的にはa Control以外のLinkLabelをへLinkLabel[]Control[]参照を通じてに追加できるという事実によるものです。これにより、ランタイム例外が発生します。

変換が行われるのは、AddRangeが必要なためControl[]です。

より一般的には、派生型のコンテナーを基本型のコンテナーに変換することは、後で概説した方法でコンテナーを変更できない場合にのみ安全です。配列はその要件を満たしていません。


5

問題の根本原因は他の回答で正しく説明されていますが、警告を解決するには、いつでも次のように書くことができます。

_list.ForEach(lnkLbl => flPanel.Controls.Add(lnkLbl));

2

VS 2008では、この警告は表示されません。これは.NET 4.0の新機能である必要があります。
明確化:Sam Mackrillによると、警告を表示するのはResharperです。

C#コンパイラはAddRange、渡された配列を変更しないことを認識していません。にAddRangeはタイプのパラメーターがあるためControl[]、理論的TextBoxにはを配列に割り当てようとする可能性があります。これはの真の配列に対して完全に正しいはずですControlが、配列は実際にはの配列でLinkLabelsあり、そのような割り当てを受け入れません。

c#で配列を共変にすることは、Microsoftの悪い決定でした。そもそも派生型の配列を基本型の配列に割り当てることができるのは良い考えのように思えるかもしれませんが、これは実行時エラーを引き起こす可能性があります!


2
私はこの警告をResharperから受け取ります
Sam Mackrill

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.