Recently I have been researching some conventions in writing functions that return a collection. I was wondering whether the function that actually uses a List<int> should return a List<int> or rather IList<int>, ICollection<int> or IEnumerable<int>. I created some tests for performance and I was quite surprised with the results.
static List<int> list = MakeList();
static IList<int> iList = MakeList();
static ICollection<int> iCollection = MakeList();
static IEnumerable<int> iEnumerable = MakeList();
public static TimeSpan Measure(Action f)
{
var stopWatch = new Stopwatch();
stopWatch.Start();
f();
stopWatch.Stop();
return stopWatch.Elapsed;
}
public static List<int> MakeList()
{
var list = new List<int>();
for (int i = 0; i < 100; ++i)
{
list.Add(i);
}
return list;
}
public static void Main()
{
var time1 = Measure(() => { // Measure time of enumerating List<int>
for (int i = 1000000; i > 0; i-- ) {
foreach (var item in list)
{
var x = item;
}
}
});
Console.WriteLine($"List<int> time: {time1}");
var time2 = Measure(() => { // IList<int>
for (int i = 1000000; i > 0; i-- ) {
foreach (var item in iList)
{
var x = item;
}
}
});
Console.WriteLine($"IList<int> time: {time2}");
var time3 = Measure(() => { // ICollection<int>
for (int i = 1000000; i > 0; i-- ) {
foreach (var item in iCollection)
{
var x = item;
}
}
});
Console.WriteLine($"ICollection<int> time: {time3}");
var time4 = Measure(() => { // IEnumerable<int>
for (int i = 1000000; i > 0; i-- ) {
foreach (var item in iEnumerable)
{
var x = item;
}
}
});
Console.WriteLine($"IEnumerable<int> time: {time4}");
}
Output:
List<int> time: 00:00:00.7976577
IList<int> time: 00:00:01.5599382
ICollection<int> time: 00:00:01.7323919
IEnumerable<int> time: 00:00:01.6075277
I've tried different order of the measures or making the MakeList() return one of the above interfaces but all of it only confirms that returning a List<int> and processing it as a List<int> is about twice as fast as with the interfaces.
However various sources, including this answer claim that you should never return List<> and always use an interface.
So my question is:
- Why is processing a
List<int>about twice as fast as the interfaces? - What should we return from a function and how to manage the code if we care about performance?