C# .First() vs [0]

一个人想着一个人 提交于 2019-12-21 04:33:09

问题


Interested, does approaches has any differences.
So, I created two snippets.

Snippet A 
List<int> a = new List<int>();
a.Add(4);
a.Add(6);
int b = a.First(); 

and

Snippet B 
List<int> a = new List<int>();
a.Add(4);
a.Add(6);
int b = a[0]; 

In IL we trust, so

Snippet A IL
IL_0000:  nop         
IL_0001:  newobj      System.Collections.Generic.List<System.Int32>..ctor
IL_0006:  stloc.0     // a
IL_0007:  ldloc.0     // a
IL_0008:  ldc.i4.4    
IL_0009:  callvirt    System.Collections.Generic.List<System.Int32>.Add
IL_000E:  nop         
IL_000F:  ldloc.0     // a
IL_0010:  ldc.i4.6    
IL_0011:  callvirt    System.Collections.Generic.List<System.Int32>.Add
IL_0016:  nop         
IL_0017:  ldloc.0     // a
IL_0018:  call        System.Linq.Enumerable.First
IL_001D:  stloc.1     // b
IL_001E:  ret        

and

Snippet B IL
IL_0000:  nop         
IL_0001:  newobj      System.Collections.Generic.List<System.Int32>..ctor
IL_0006:  stloc.0     // a
IL_0007:  ldloc.0     // a
IL_0008:  ldc.i4.4    
IL_0009:  callvirt    System.Collections.Generic.List<System.Int32>.Add
IL_000E:  nop         
IL_000F:  ldloc.0     // a
IL_0010:  ldc.i4.6    
IL_0011:  callvirt    System.Collections.Generic.List<System.Int32>.Add
IL_0016:  nop         
IL_0017:  ldloc.0     // a
IL_0018:  ldc.i4.0    
IL_0019:  callvirt    System.Collections.Generic.List<System.Int32>.get_Item
IL_001E:  stloc.1     // b
IL_001F:  ret  

Snippet B produced one command more IL, but which approach faster in the end?


回答1:


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.




回答2:


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).




回答3:


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).




回答4:


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.




回答5:


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.



来源:https://stackoverflow.com/questions/33544269/c-sharp-first-vs-0

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!