列表的通用列表,将 List> 转换为 IList>

本文关键字:IList 列表 List 转换 | 更新日期: 2023-09-27 18:31:44

我们使用了一个对3D测量数据执行计算的类库,它公开了一个方法:

MeasurementResults Calculate(IList<IList<Measurement>> data)

我想允许使用任何可索引的列表列表(当然是测量)调用此方法,例如两者:

Measurement[][] array;
List<List<Measurement>> list;

使用数组调用该方法工作正常,这有点奇怪。这里有一些编译器技巧吗?尝试使用 List 调用会给出熟悉的错误:

cannot convert from 'List<List<Measurement>>' to 'IList<IList<Measurement>>'

因此,我编写了一个外观类(也包含一些其他内容),该方法在参数和方法之间拆分泛型定义,并在必要时转换为 IList 类型:

MeasurementResults Calculate<T>(IList<T> data) where T : IList<Measurement>
{
  IList<IList<Measurement>> converted = data as IList<IList<Measurement>>;
  if(converted == null)
    converted = data.Select(o => o as IList<Measurement>).ToList();
  return Calculate(converted);
}

这是解决问题的好方法,还是您有更好的主意?

另外,在测试该问题的不同解决方案时,我发现如果类库方法是用IEnumerable而不是IList声明的,则可以同时使用数组和List调用该方法:

MeasurementResults Calculate(IEnumerable<IEnumerable<Measurement>> data)

怀疑又有一些编译器技巧在起作用,我想知道为什么他们在工作时没有IList List一起工作?

列表的通用列表,将 List<List<T>> 转换为 IList<IList<T>>

可以用 IEnumerable<T> 执行此操作,因为这在T中是协变的。用IList<T>这样做是不安全的,因为它同时用于"输入"和"输出"。

具体而言,请考虑:

List<List<Foo>> foo = new List<List<Foo>>();
List<IList<Foo>> bar = foo;
bar.Add(new Foo[5]); // Arrays implement IList<T>
// Eh?
List<Foo> firstList = foo[0];

有关泛型协方差和逆变的详细信息,请参阅 MSDN。

假设我们有一个方法

void Bar(IList<IList<int>> foo)

Bar内,向foo添加int[]应该是完全可以接受的——毕竟,int[]实现了IList<int>,不是吗?

但是如果我们用List<List<int>>调用Bar,我们现在将尝试为只接受List<int>的东西添加一个int[]!这将是糟糕的。所以编译器不允许您这样做。

另外,在测试该问题的不同解决方案时,我发现如果类库方法是用IEnumerable而不是IList声明的,则可以同时使用数组和List调用该方法:

事实上,因为如果行为契约只是说"我可以输出int s",那么什么都不会出错。进一步研究的关键术语是协方差逆变,没有人*能记住哪个是哪个。

在您的特定情况下,如果 Calculate读取其输入,则将其更改为消耗IEnumerable绝对是正确的做法 - 它既允许您传入任何符合条件的对象,又进一步向读取签名的任何人传达此方法有意设计为仅消耗,而不是突变, 它的输入。


*好吧,主要是没有人

您的实现中有一些事情我会改变。首先,它可能会在原始对象中存在对象的结果中插入空值。就个人而言,我更喜欢它扔。

MeasurementResults Calculate<T>(IList<T> data) where T : IList<Measurement>
{
    return Calculate(data as IList<IList<Measurement>>
                     ?? data.Cast<IList<Measurement>>().ToList());
}

一个简短的例子就像我个人会在你的情况下做的事情一样,尽管我怀疑我是否会真正实现该方法。界面是这样写的(我希望)是有原因的,我会尝试在我的实现中使用这些知识,而不是尽可能对抗它。如果使用代码(或类似内容),则对方法中的列表所做的任何更改都不会反映在原始列表中。这是一个传递给该方法的新列表,由于签名要求 IList,因此可以进行更改。

除了列表中没有可能包含 null 而不是对象之外,我还更改为使用 cast 方法,因为这基本上是您想要完成的。(强制转换不允许自定义转换运算符)。