从x到y的共变阵列转换可能会导致运行时异常

时间:2012-01-02 19:00:18

标签: c# winforms .net-4.0 covariance

我有一个private readonlyLinkLabel列表(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. 这是一个用户控件,多个对象不会访问它来设置标签,  所以保持代码不会影响它。

7 个答案:

答案 0 :(得分:140)

这意味着什么

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分配给数组也是合法的编译时间。问题是数组实际上不是一个控件数组。在运行时,它仍然是一个LinkLabel数组。因此,赋值或写入将引发异常。

答案 1 :(得分:12)

我会试着澄清Anthony Pegram的答案。

泛型类型在返回某个类型参数时是协变的,当它返回所述类型的值时(例如Func<out TResult>返回TResult的实例,IEnumerable<out T>返回T的实例)。也就是说,如果某些内容返回TDerived的实例,您也可以使用TBase这样的实例。

当接受所述类型的值时,通用类型在某些类型参数上是逆变的(例如Action<in TArgument>接受TArgument的实例)。也就是说,如果某些内容需要TBase的实例,您也可以传递TDerived的实例。

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

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

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

答案 2 :(得分:9)

警告是由于您理论上可以通过ControlLinkLabel LinkLabel[]Control[]添加AddRange以外的Control[]。这会导致运行时异常。

转换发生在此处,因为{{1}}需要{{1}}。

更一般地说,如果您不能按照刚刚概述的方式修改容器,则只能将派生类型的容器转换为基本类型的容器。数组不满足该要求。

答案 3 :(得分:8)

最直接的“解决方案”

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

现在,由于您正在将List<LinkLabel>更改为IEnumerable<Control>,因此不再需要关注,因为无法将项目“添加”到可枚举项。

答案 4 :(得分:5)

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

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

答案 5 :(得分:2)

使用VS 2008,我没有收到此警告。这必须是.NET 4.0的新功能 澄清:根据Sam Mackrill的说法,Resharper会发出警告。

C#编译器不知道AddRange不会修改传递给它的数组。由于AddRange的参数类型为Control[],因此理论上可以尝试为数组分配TextBox,这对于Control的真数组来说是完全正确的,但是数组实际上是LinkLabels的数组,不会接受这样的赋值。

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

答案 6 :(得分:1)

这个怎么样?

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