我们正在使用一个对3D测量数据进行计算的类库,它公开了一种方法:
MeasurementResults Calculate(IList<IList<Measurement>> data)
我想允许使用任何可索引的列表列表(当然是Measurement)调用此方法,例如:
Measurement[][] array;
List<List<Measurement>> list;
使用数组调用方法工作正常,这有点奇怪。这里有一些编译技巧吗?尝试使用List
进行调用会产生熟悉的错误:
cannot convert from 'List<List<Measurement>>' to 'IList<IList<Measurement>>'
所以,我编写了一个facade类(包含其他一些东西),并使用一个方法在参数和方法之间拆分泛型定义,并在必要时转换为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
时没有使用它们?
答案 0 :(得分: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];
有关generic covariance and contravariance的详细信息,请参阅MSDN。
答案 1 :(得分:3)
假设我们有一个方法
void Bar(IList<IList<int>> foo)
在Bar
内,应该完全可以将int[]
添加到foo
- 毕竟int[]
实现IList<int>
,不是吗?
但如果我们使用Bar
调用List<List<int>>
,我们现在会尝试将int[]
添加到只接受List<int>
的内容中!这将是坏。所以编译器不允许你这样做。
此外,在测试问题的不同解决方案时,我发现如果类库方法已使用
IEnumerable
而不是IList
声明,则可以使用两个数组调用该方法和List
:
确实,因为如果行为合同只是说“我可以输出 int
s”,那么什么都不会出错。进一步研究的关键术语是协方差和逆变,没有一个 * 能记住哪个是哪个。
在您的特定情况下,如果Calculate
只是正在阅读其输入,将其更改为使用IEnumerable
绝对是正确的做法 - 它都允许您通过任何符合条件的对象,并进一步传达给任何阅读签名的人,该方法有意设计为仅消费,而不是改变其输入。
* 好吧,大多数没有人
答案 2 :(得分: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不允许使用自定义转换运算符。)