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

The*_*iot 135 c# .net-4.0 covariance winforms

我有一个s()private readonly列表.我稍后在此列表中添加s并将这些标签添加到以下内容中:LinkLabelIList<LinkLabel>LinkLabelFlowLayoutPanel

foreach(var s in strings)
{
    _list.Add(new LinkLabel{Text=s});
}

flPanel.Controls.AddRange(_list.ToArray());
Run Code Online (Sandbox Code Playgroud)

Resharper给我一个警告:Co-variant array conversion from LinkLabel[] to Control[] can cause run-time exception on write operation.

请帮我弄明白:

  1. 这意味着什么?
  2. 这是一个用户控件,多个对象不会访问它来设置标签,因此保持代码不会影响它.

Ant*_*ram 147

这意味着什么

Control[] controls = new LinkLabel[10]; // compile time legal
controls[0] = new TextBox(); // compile time legal, runtime exception
Run Code Online (Sandbox Code Playgroud)

而且更笼统地说

string[] array = new string[10];
object[] objs = array; // legal at compile time
objs[0] = new Foo(); // again legal, with runtime exception
Run Code Online (Sandbox Code Playgroud)

在C#中,您可以引用一个对象数组(在您的情况下,LinkLabels)作为基类型的数组(在本例中,作为一个控件数组).将编译的另一个对象分配给Control数组也是合法的编译时间.问题是该数组实际上不是一个控件数组.在运行时,它仍然是一个LinkLabel数组.因此,赋值或写入将引发异常.

  • 如果有人想知道为什么数组在C#中是错误协变的,那么[Eric Lippert的解释](http://blogs.msdn.com/b/ericlippert/archive/2007/10/17/covariance-and-contravariance-in-c- part-two-array-covariance.aspx):_它被添加到CLR中,因为Java需要它,而CLR设计者希望能够支持类似Java的语言.然后我们将它添加到C#,因为它在CLR中.这个决定当时颇具争议,我对此并不高兴,但现在我们无能为力._ (6认同)
  • 是的,从LinkLabel转换为Control是合法的,但这与此处发生的情况不同.这是关于从`LinkLabel []`转换为`Control []`的警告,这仍然是合法的,但可能有运行时问题.所有改变的是引用数组的方式.数组本身没有改变.看到这个问题?该数组仍然是派生类型的数组.引用是通过基类型的数组.因此,为基类型分配元素是合法的编译时间.但运行时类型不支持它. (2认同)

pen*_*tur 14

我会试着澄清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[]从原来的列表复制的数值,并且,摆脱(合理的)警告的,你需要在传递Control[]AddRange.list.ToArray<Control>()将完成工作:ToArray<TSource>接受IEnumerable<TSource>作为其论点并返回TSource[]; List<LinkLabel>实现只读IEnumerable<out LinkLabel>,由于IEnumerable协方差,可以传递给接受IEnumerable<Control>作为其参数的方法.


Chr*_*sic 11

最直接的"解决方案"

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

现在既然你正在改变List<LinkLabel>,IEnumerable<Control>那就不再需要关注,因为无法将一个项目"添加"到一个可枚举的项目中.


Stu*_*etz 9

该警告是由于这样的事实,你可以添加理论上一个Control比其他LinkLabelLinkLabel[]通过Control[]引用.这会导致运行时异常.

转换发生在这里因为AddRange需要a Control[].

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


Tim*_*ams 5

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

_list.ForEach(lnkLbl => flPanel.Controls.Add(lnkLbl));
Run Code Online (Sandbox Code Playgroud)