6

我们正在使用一个对 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时没有使用它?

4

3 回答 3

6

可以使用 来执行此操作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 。

于 2012-04-19T14:15:07.717 回答
3

假设我们有一个方法

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

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

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

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

事实上,因为如果行为契约只是说“我可以输出 ints”,那么就不会出错。进一步研究的关键术语是协变逆变,没有人能记住哪个是哪个

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


*好吧,几乎没有人

于 2012-04-19T14:21:12.153 回答
1

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

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

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

除了列表中可能没有空值而不是对象之外,我已经更改为使用 cast 方法,因为这基本上就是您要完成的工作。(Cast 不允许自定义转换运算符)。

于 2012-04-19T14:23:09.197 回答