Co-variant array conversion from x to y may cause run-time exception
What it means is this
Control[] controls = new LinkLabel[10]; // compile time legal
controls[0] = new TextBox(); // compile time legal, runtime exception
And in more general terms
string[] array = new string[10];
object[] objs = array; // legal at compile time
objs[0] = new Foo(); // again legal, with runtime exception
In C#, you are allowed to reference an array of objects (in your case, LinkLabels) as an array of a base type (in this case, as an array of Controls). It is also compile time legal to assign another object that is a Control
to the array. The problem is that the array is not actually an array of Controls. At runtime, it is still an array of LinkLabels. As such, the assignment, or write, will throw an exception.
I'll try to clarify Anthony Pegram answer.
Generic type is covariant on some type argument when it returns values of said type (e.g. Func<out TResult>
returns instances of TResult
, IEnumerable<out T>
returns instances of T
). That is, if something returns instances of TDerived
, you can as well work with such instances as if they were of TBase
.
Generic type is contravariant on some type argument when it accepts values of said type (e.g. Action<in TArgument>
accepts instances of TArgument
). That is, if something needs instances of TBase
, you can as well pass in instances of TDerived
.
It seems quite logical that generic types which both accept and return instances of some type (unless it is defined twice in the generic type signature, e.g. CoolList<TIn, TOut>
) are not covariant nor contravariant on the corresponding type argument. For example, List
is defined in .NET 4 as List<T>
, not List<in T>
or List<out T>
.
Some compatibility reasons might have caused Microsoft to ignore that argument and make arrays covariant on their values type argument. Maybe they conducted an analysis and found that most people only use arrays as if they were readonly (that is, they only use array initializers to write some data into an array), and, as such, the advantages overweigh the disadvantages caused by possible runtime errors when someone will try to make use of covariance when writing into the array. Hence it is allowed but not encouraged.
As for your original question, list.ToArray()
creates a new LinkLabel[]
with values copied from original list, and, to get rid of (reasonable) warning, you'll need to pass in Control[]
to AddRange
. list.ToArray<Control>()
will do the job: ToArray<TSource>
accepts IEnumerable<TSource>
as its argument and returns TSource[]
; List<LinkLabel>
implements read-only IEnumerable<out LinkLabel>
, which, thanks to IEnumerable
covariance, could be passed to the method accepting IEnumerable<Control>
as its argument.
The most straight forward "solution"
flPanel.Controls.AddRange(_list.AsEnumerable());
Now since you are covariantly changing List<LinkLabel>
to IEnumerable<Control>
there is no more concerns since it is not possible to "add" an item to an enumerable.
The warning is due to the fact that you could theoretically add a Control
other than a LinkLabel
to the LinkLabel[]
through the Control[]
reference to it. This would cause a runtime exception.
The conversion is happening here because AddRange
takes a Control[]
.
More generally, converting a container of a derived type to a container of a base type is only safe if you can't subsequently modify the container in the way just outlined. Arrays do not satisfy that requirement.