We’re using a class library that performs calculations on 3D measurement data, it exposes a method:
MeasurementResults Calculate(IList<IList<Measurement>> data)
I would like to allow calling this method with any indexable list of lists (of Measurement of course), for example both:
Measurement[][] array;
List<List<Measurement>> list;
Calling the method using the array works fine, which is a bit strange. Is there some compiler trick at work here? Trying to call with the List gives the familiar error:
cannot convert from 'List<List<Measurement>>' to 'IList<IList<Measurement>>'
So, I have written a facade class (containing some other things as well), with a method that splits the generic definition between the argument and method and converts to the IList type if necessary:
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);
}
Is this a good way to solve the problem, or do you have a better idea?
Also, while testing different solutions to the problem, I found out that if the class library method had been declared with IEnumerable instead of IList, it is ok to call the method using both the array and the List:
MeasurementResults Calculate(IEnumerable<IEnumerable<Measurement>> data)
I suspect that there is some compiler trick at work again, I wonder why they haven’t made IList work with List while they were at it?
Suppose we have a method
Within
Bar, it should then be perfectly admissible to add aint[]tofoo– after all,int[]implementsIList<int>, doesn’t it?But if we had called
Barwith aList<List<int>>, we would now be trying to add aint[]to something that only accepts aList<int>! which would be bad. So the compiler doesn’t let you do this.Indeed, because if the behavioural contract just says “I can output
ints”, nothing can go wrong. The key terms for further research are covariance and contravariance, and no one* can ever remember which is which.In your particular case, if
Calculateis only ever reading its input, changing it to consumeIEnumerableis absolutely the right thing to do – it both allows you to pass in any qualifying objects, and it further communicates to anyone reading the signature that this method is intentionally designed to only consume, not mutate, its input.* well, mostly no one