Interested, does approaches has any differences.
So, I created two snippets.
Snippet A
List a = new List();
a.Add(4);
a.Add(6);
int
If you're asking about asymptotic complexity, both approaches are O(1)
, use any of it.
If you're asking about real speed, there's no answer, since it can differ from version to version, from one machine to another. IL you've generated is not the same for any other version of .NET.
Attempt to optimize your code by choosing one of these approaches is obviously premature optimization.
The Enumerable.First method is defined as
public static TSource First<TSource>(this IEnumerable<TSource> source)
{
if (source == null) throw Error.ArgumentNull("source");
IList<TSource> list = source as IList<TSource>;
if (list != null) {
if (list.Count > 0) return list[0];
}
else {
using (IEnumerator<TSource> e = source.GetEnumerator()) {
if (e.MoveNext()) return e.Current;
}
}
throw Error.NoElements();
}
So for a List<T>
it ends up using the indexer after a null check and a cast. Seems not much, but when I tested the performance, First
was 10x slower than the indexer (for
loop, 10 000 000 iterations, release build: First - 100 ms, indexer - 10 ms).
In general, concrete class/interface methods should be favored over generic implementations because, well, the later are generic and the data structure is supposed to take its specifics into account. For instance, linked list should not provide indexer because it cannot be implemented efficiently. Ideally, every data structure will define a its own method with the same signature as the corresponding generic extension method when it can provide better implementation, and compiler will handle that properly. This can be treated as specialization and unfortunately is not supported very well as in C++ templates. The implementation of Enumerable.First
is a good example of a "workaround" rather than a solution - it does optimization for a specific BCL interface, but cannot handle a custom data structure (like linked list) which can provide the same information much better than using the generic implementation. And it's even worse for the Enumerable.Last
.
To resume, if you program against specific classes/interfaces, use their methods when possible. If you are programming against standard generic interfaces, well, you have no other options anyway (except defining your extension methods that shadow the standard ones, but that usually leads to clashes).
Tested in LINQPad 5 with following code:
var sw = Stopwatch.StartNew();
for(int i = 0; i < 1000000000; i++)
{
List<int> a = new List<int>();
a.Add(i);
a.Add(i+2);
int b = a.First();//[0] for B
}
sw.Stop();
Console.WriteLine(sw.ElapsedTicks);
.First()
gave 01:04.021 and 0:45.794 with optimization.
[0]
gave 0:44.288, 0:27.968 with optimization and better code for me, as I think.
Really, for me [0]
is more readable than .First()
and usually I don't need checks, provided by him.
So, in most cases I'll choose [0]
.
Thanks.
You can check it by yourself :
static void Main()
{
List<long> resultsFirst = new List<long>();
List<long> resultsIndex = new List<long>();
Stopwatch s = new Stopwatch();
for (int z = 0; z < 100; z++)
{
List<int>[] lists = new List<int>[10000];
int temp = 0;
for (int i = 0; i < lists.Length; i++)
lists[i] = new List<int>() { 4, 6 };
s.Restart();
for (int i = 0; i < lists.Length; i++)
temp = lists[i].First();
s.Stop();
resultsFirst.Add(s.ElapsedTicks);
s.Restart();
for (int i = 0; i < lists.Length; i++)
temp = lists[i][0];
s.Stop();
resultsIndex.Add(s.ElapsedTicks);
}
Console.WriteLine("LINQ First() : " + resultsFirst.Average());
Console.WriteLine(Environment.NewLine);
Console.WriteLine("By index : " + resultsIndex.Average());
Console.ReadKey();
}
Output in Release mode :
LINQ First() : 367
By index : 84
Output in debug mode :
LINQ First() : 401
By index : 177
P.S.
The source code for method First is:
public static TSource First<TSource>(this IEnumerable<TSource> source)
{
IList<TSource> list = source as IList<TSource>;
if (list != null)
{
if (list.Count > 0)
{
return list[0];
}
}
else
{
using (IEnumerator<TSource> enumerator = source.GetEnumerator())
{
if (enumerator.MoveNext())
{
return enumerator.Current;
}
}
}
}
The casting operation source as IList<TSource>
or creating an Enumerator object is very likely the reason why First()
is considerably slower.