从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
。
请帮我弄清楚:
- 这是什么意思?
- 这是一个用户控件,不会被多个对象访问来设置标签,所以保持代码不会影响它。
这意味着什么
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[]
传递给AddRange
。 list.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());