Nota: Este post ha sido importado de mi blog de geeks.ms. Es posible que algo no se vea del todo "correctamente". En cualquier caso puedes acceder a la versión original aquí
Te has preguntado alguna vez la diferencia de rendimiento que pueda haber entre el método extensor Count() proporcionado por LINQ y la propiedad Count de la interfaz IList
Es decir dado el siguiente código:
List<int> lst = new List<int>();
// Añadimos ints a la lista...
// Qué es más rápido?
var count = lst.Count;
var count2 = ((IEnumerable<int>)lst).Count();
<p>
A veces hacemos <em>suposiciones</em> sobre como funciona LINQ to objects. Uno puede pensar que el método Count() de LINQ está definido como:
</p>
<div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper">
<pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">public</span> <span style="color: #0000ff">static</span> <span style="color: #0000ff">int</span> Count<T>(<span style="color: #0000ff">this</span> IEnumerable<T> @<span style="color: #0000ff">this</span>)<br />{<br /> <span style="color: #0000ff">int</span> count = 0;<br /> <span style="color: #0000ff">foreach</span> (var x <span style="color: #0000ff">in</span> @<span style="color: #0000ff">this</span>) count++;<br /> <span style="color: #0000ff">return</span> count;<br />}</pre>
<p>
</div>
<p>
Hay gente que basándose en estas suposiciornes intenta evitar el uso de Count() cuando sabe que la colección real es una List<T> p.ej. Desgraciadamente esto les lleva a no poder hacer métodos genéricos con IEnumerable<T> (empiezan a trabajar con IList<T>). A veces comentan que usarían mucho más LINQ to Objects, pero que trabajan habitualmente con listas, y que no pueden permitirse el sobrecoste de recorrer toda la lista simplemente para contar los elementos, cuando la clase List<T> ya tiene una propiedad para ello…
</p>
<p>
… están totalmente equivocados.
</p>
<p>
LINQ to Objects está <em>optimizado</em>, no es un proveedor tan tonto como algunos piensan… Así realmente si el objeto sobre el que usamos Count() implementa ICollection o ICollection<T>, LINQ usará la propiedad Count directamente, sin recorrer los elementos.
</p>
<p>
Para que veais que es cierto he realizado un pequeño test:
</p>
<div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper">
<pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">class</span> Program<br />{<br /> <span style="color: #0000ff">static</span> <span style="color: #0000ff">void</span> Main(<span style="color: #0000ff">string</span>[] args)<br /> {<br /> List<<span style="color: #0000ff">int</span>> list = <span style="color: #0000ff">new</span> List<<span style="color: #0000ff">int</span>>();<br /> <span style="color: #0000ff">for</span> (<span style="color: #0000ff">int</span> i = 0; i < 10000000; i++)<br /> {<br /> list.Add(i);<br /> }<br /><br /> Stopwatch sw = <span style="color: #0000ff">new</span> Stopwatch();<br /> sw.Start();<br /> CountList(list);<br /> sw.Stop();<br /> Console.WriteLine(<span style="color: #006080">"List.Count:"</span> + sw.ElapsedMilliseconds);<br /> sw.Reset();<br /> sw.Start();<br /> CountLinq(list);<br /> sw.Stop();<br /> Console.WriteLine(<span style="color: #006080">"LINQ.Count():"</span> + sw.ElapsedMilliseconds);<br /> sw.Reset();<br /> sw.Start();<br /> CountLoop(list);<br /> sw.Stop();<br /> Console.WriteLine(<span style="color: #006080">"foreach count"</span> + sw.ElapsedMilliseconds);<br /> sw.Reset();<br /> Console.ReadLine();<br /> }<br /><br /> <span style="color: #0000ff">static</span> <span style="color: #0000ff">void</span> CountList (IList<<span style="color: #0000ff">int</span>> list)<br /> {<br /> <span style="color: #0000ff">for</span> (<span style="color: #0000ff">int</span> i=0; i< 100; i++)<br /> {<br /> var a = list.Count;<br /> }<br /> }<br /><br /> <span style="color: #0000ff">static</span> <span style="color: #0000ff">void</span> CountLinq(IEnumerable<<span style="color: #0000ff">int</span>> list)<br /> {<br /> <span style="color: #0000ff">for</span> (<span style="color: #0000ff">int</span> i = 0; i < 100; i++)<br /> {<br /> var a = list.Count();<br /> }<br /> }<br /><br /> <span style="color: #0000ff">static</span> <span style="color: #0000ff">void</span> CountLoop(IEnumerable<<span style="color: #0000ff">int</span>> list)<br /> {<br /> <span style="color: #0000ff">for</span> (<span style="color: #0000ff">int</span> i = 0; i < 100; i++)<br /> {<br /> var a = list.Count2();<br /> }<br /> }<br />}</pre>
<p>
</div>
<p>
El test cuenta 100 veces una lista con 10 millones de elementos, y cuenta lo que se tarda usando la propiedad Count de la lista, el método Count() de LINQ y el método Count2, que es un método extensor que recorre la lista (es exactamente el mismo método que he puesto antes).
</p>
<p>
Los resultados no dejan lugar a dudas:
</p>
<ol>
<li>
Usando la propiedad Count, se tarda menos de un ms en contar 100 veces la lista.
</li>
<li>
Usando el método Count() de LINQ se tarda igualmente menos de un ms en contar la lista 100 veces.
</li>
<li>
Usando el método extensor Count2 se tarda más de 9 segundos en contar la lista 100 veces…
</li>
</ol>
<p>
Si en lugar de 100 veces la contamos diez millones de veces, los resultados son:
</p>
<ol>
<li>
30 ms usando la propiedad Count
</li>
<li>
247 ms usando el método Count() de LINQ
</li>
<li>
Ni idea usando el método extensor Count2… pero vamos si para 100 veces ha tardado 9 segundos… para diez millones… no quiero ni pensarlo!
</li>
</ol>
<p>
Los tiempos han sido medidos con la aplicación en <em>Release</em>.
</p>
<p>
La conclusión es clara: no tengáis miedo a LINQ, que MS no ha hecho algo tan cutre como un triste foreach!! 😉
</p>
<p>
Saludos!
</p>
<p>
PD: <a href="http://blogs.msdn.com/csharpfaq/archive/2009/01/26/does-the-linq-to-objects-provider-have-built-in-performance-optimization.aspx">En este post del blog del equipo de C# cuentan esta y otras optimizaciones más de LINQ to Objects</a>… lectura imprescindible! 🙂
</p>