Co-variant array conversion from x to y may cause run-time exception

前端 未结 7 1350
感动是毒
感动是毒 2020-12-23 15:37

I have a private readonly list of LinkLabels (IList). I later add LinkLabels to this list and add those

相关标签:
7条回答
  • 2020-12-23 16:06

    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.

    0 讨论(0)
  • 2020-12-23 16:07

    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.

    0 讨论(0)
  • 2020-12-23 16:08

    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.

    0 讨论(0)
  • 2020-12-23 16:11

    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.

    0 讨论(0)
  • 2020-12-23 16:12

    How about this?

    flPanel.Controls.AddRange(_list.OfType<Control>().ToArray());
    
    0 讨论(0)
  • 2020-12-23 16:18

    With VS 2008, I am not getting this warning. This must be new to .NET 4.0.
    Clarification: according to Sam Mackrill it's Resharper who displays a warning.

    The C# compiler does not know that AddRange will not modify the array passed to it. Since AddRange has a parameter of type Control[], it could in theory try to assign a TextBox to the array, which would be perfectly correct for a true array of Control, but the array is in reality an array of LinkLabels and will not accept such an assignment.

    Making arrays co-variant in c# was a bad decision of Microsoft. While it might seem a good idea to be able to assign an array of a derived type to an array of a base type in the first place, this can lead to runtime errors!

    0 讨论(0)
提交回复
热议问题