从x到y的共变数组转换可能导致运行时exception

我有一个private readonly列表( IList<LinkLabel> )。 我后来将LinkLabel添加到此列表中,并将这些标签添加到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. 这是一个用户控件,不会被多个对象访问来设置标签,所以保持代码不会影响它。

这意味着什么

 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)作为一个基本types的数组(在这种情况下,作为一个控件数组)。 将另一个作为Control对象分配给数组也是合法的。 问题是该数组实际上不是一个控件的数组。 在运行时,它仍然是一个LinkLabels数组。 因此,分配或写入将引发exception。

我会试着澄清安东尼·佩格拉姆的答案。

genericstypes在返回某个types参数时是协变的(例如Func<out TResult>返回TResult实例, IEnumerable<out T>返回T实例)。 也就是说,如果某个事件返回了TDerived实例,那么也可以像这样的实例一样使用TBase

genericstypes在接受所述types的值时(例如Action<in TArgument>接受TArgument实例),在某些types参数上是逆变的。 也就是说,如果某件事需要TBase实例,那么也可以通过TDerived实例。

似乎非常合乎逻辑的是,接受和返回某种types实例(除非在genericstypes签名中定义了两次,例如CoolList<TIn, TOut> )的genericstypes不是协变的,也不是对应的types参数的逆变。 例如, List在.NET 4中定义为List<T> ,而不是List<in T>List<out T>

某些兼容性原因可能导致Microsoft忽略该参数,并使数组协变为其值types参数。 也许他们进行了一个分析,发现大多数人只使用数组,就好像它们是只读的(也就是说,它们只使用数组初始化器将一些数据写入数组),因此,这些优点超过了可能的运行时当写入数组时,有人会尝试使用协方差的错误。 因此,允许但不鼓励。

至于你的原始问题, list.ToArray()创build一个新的LinkLabel[] ,从原始列表中复制的值,为了摆脱(合理的)警告,你需要将Control[]传递给AddRangelist.ToArray<Control>()将完成这个工作: ToArray<TSource>接受IEnumerable<TSource>作为它的参数并返回TSource[] ; List<LinkLabel>实现只读的IEnumerable<out LinkLabel> ,由于IEnumerable协方差,它可以传递给接受IEnumerable<Control>作为参数的方法。

这个警告是由于这样一个事实,即理论上可以通过Control[]引用向LinkLabel[]添加一个除LinkLabel以外的Control[] 。 这将导致运行时exception。

转换发生在这里,因为AddRange需要一个Control[]

更一般地说,将派生types的容器转换为基types的容器是安全的,如果您不能按照上述方式修改容器。 数组不满足这个要求。

最直接的“解决scheme”

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

现在,因为您将List<LinkLabel>更改为IEnumerable<Control>所以没有更多关注,因为无法将某个项“添加”到可枚举项中。

问题的根本原因在其他答案中正确描述,但要解决警告,您可以随时写:

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

与VS 2008,我没有得到这个警告。 这对.NET 4.0来说必须是新的。
澄清:根据Sam Mackrill,Resharper显示了警告。

C#编译器不知道AddRange将不会修改传递给它的数组。 由于AddRange具有Control[]types的参数,理论上可以尝试将一个TextBox分配给该数组,这对于一个真正的Control数组是完全正确的,但是该数组实际上是一个LinkLabels数组,并且不会接受这样的任务。

在c#中制作数组是微软的一个糟糕的决定。 虽然首先能够将派生types的数组分配给基types的数组似乎是一个好主意,但这可能会导致运行时错误!

这个怎么样?

 flPanel.Controls.AddRange(_list.OfType<Control>().ToArray());