Double Possible:
Find() vs Où().FirstorDefault()A obtenu un intéressant résultat de la recherche pour Diana, au sein d'une grande séquence d'un simple type référence ayant une chaîne unique de la propriété.
Stopwatch watch = new Stopwatch(); string diana = "Diana"; while (Console.ReadKey().Key != ConsoleKey.Escape) { //Armour with 1000k++ customers. Wow, should be a product with a great success! :) var customers = (from i in Enumerable.Range(0, 1000000) select new Customer { Name = Guid.NewGuid().ToString() }).ToList(); customers.Insert(999000, new Customer { Name = diana }); // Putting Diana at the end :) //1. System.Linq.Enumerable.DefaultOrFirst() watch.Restart(); customers.FirstOrDefault(c => c.Name == diana); watch.Stop(); Console.WriteLine("Diana was found in {0} ms with System.Linq.Enumerable.FirstOrDefault().", watch.ElapsedMilliseconds); //2. System.Collections.Generic.List<T>.Find() watch.Restart(); customers.Find(c => c.Name == diana); watch.Stop(); Console.WriteLine("Diana was found in {0} ms with System.Collections.Generic.List<T>.Find().", watch.ElapsedMilliseconds); }
Est-ce à cause de pas d'Énumérateur de frais généraux dans la Liste.Find() ou ce plus peut-être quelque chose d'autre?
Find()
fonctionne à peu près deux fois plus vite, en espérant .Net de l'équipe ne marque pas Obsolète dans l'avenir.
- Trouver () contre Où (). FirstorDefault () (3 réponses )
Réponses
Trop de publicités?J'ai été en mesure d'imiter vos résultats donc j'ai décompilé votre programme et il y a une différence entre Find
et FirstOrDefault
.
Tout d'abord voici la décompilation du programme. J'ai fait votre objet de données d'une anonmyous élément de données juste pour la compilation
List<\u003C\u003Ef__AnonymousType0<string>> source = Enumerable.ToList(Enumerable.Select(Enumerable.Range(0, 1000000), i =>
{
var local_0 = new
{
Name = Guid.NewGuid().ToString()
};
return local_0;
}));
source.Insert(999000, new
{
Name = diana
});
stopwatch.Restart();
Enumerable.FirstOrDefault(source, c => c.Name == diana);
stopwatch.Stop();
Console.WriteLine("Diana was found in {0} ms with System.Linq.Enumerable.FirstOrDefault().", (object) stopwatch.ElapsedMilliseconds);
stopwatch.Restart();
source.Find(c => c.Name == diana);
stopwatch.Stop();
Console.WriteLine("Diana was found in {0} ms with System.Collections.Generic.List<T>.Find().", (object) stopwatch.ElapsedMilliseconds);
La clé de la chose à noter ici est que l' FirstOrDefault
est appelée sur Enumerable
alors qu' Find
s'appelle une méthode sur la liste source.
Alors, qu'est-ce que trouver en train de faire? C'est le décompilé Find
méthode
private T[] _items;
[__DynamicallyInvokable]
public T Find(Predicate<T> match)
{
if (match == null)
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
for (int index = 0; index < this._size; ++index)
{
if (match(this._items[index]))
return this._items[index];
}
return default (T);
}
C'est donc une itération sur un tableau d'éléments qui fait sens, comme une liste est un wrapper sur un tableau.
Toutefois, FirstOrDefault
, sur l' Enumerable
classe, utilise foreach
pour itérer les éléments. Il utilise un itérateur à la liste et passer à côté. Je pense que ce que vous voyez est la surcharge de l'itérateur
[__DynamicallyInvokable]
public static TSource FirstOrDefault<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
if (source == null)
throw Error.ArgumentNull("source");
if (predicate == null)
throw Error.ArgumentNull("predicate");
foreach (TSource source1 in source)
{
if (predicate(source1))
return source1;
}
return default (TSource);
}
Foreach est juste syntatic sucre sur l'utilisation de l'énumérable modèle. Regardez cette image
.
J'ai cliqué sur une boucle foreach pour voir ce qu'il fait et vous pouvez le voir dotpeek veut m'emmener à l'agent recenseur/en cours/suivant les implémentations qui a du sens.
Sauf qu'ils sont fondamentalement les mêmes (les tests passés dans prédicat pour voir si un élément est ce que vous voulez)
Je suis le pari que FirstOrDefault
est en cours d'exécution via l' IEnumerable
mise en œuvre, qui est, il utilisera la norme foreach
boucle pour effectuer la vérification. List<T>.Find()
n'est pas une partie de Linq (http://msdn.microsoft.com/en-us/library/x0b5b5bc.aspx), et il est probable qu'à l'aide d'une norme for
boucle d' 0
de Count
(ou un autre rapide mécanisme interne probablement d'exploitation directement sur son interne/tableau renvoyé à la ligne). En se débarrassant de la surcharge de l'énumération d' (et de faire la version des vérifications pour s'assurer que la liste n'a pas été modifié) l' Find
méthode est plus rapide.
Si vous ajoutez un troisième test:
//3. System.Collections.Generic.List<T> foreach
Func<Customer, bool> dianaCheck = c => c.Name == diana;
watch.Restart();
foreach(var c in customers)
{
if (dianaCheck(c))
break;
}
watch.Stop();
Console.WriteLine("Diana was found in {0} ms with System.Collections.Generic.List<T> foreach.", watch.ElapsedMilliseconds);
Qui s'exécute sur la même vitesse que le premier (25ms vs 27 ms est observée pour FirstOrDefault
)
EDIT: Si j'ajoute une boucle de tableau, c'est assez proche de l' Find()
de la vitesse, et compte tenu de @devshorts coup d'oeil au code source, je pense que c'est ça:
//4. System.Collections.Generic.List<T> for loop
var customersArray = customers.ToArray();
watch.Restart();
int customersCount = customersArray.Length;
for (int i = 0; i < customersCount; i++)
{
if (dianaCheck(customers[i]))
break;
}
watch.Stop();
Console.WriteLine("Diana was found in {0} ms with an array for loop.", watch.ElapsedMilliseconds);
Cela va seulement 5,5% plus lent que l' Find()
méthode.
Donc la ligne en bas: une boucle sur les éléments du tableau est plus rapide que de traiter avec des foreach
itération de frais généraux. (mais les deux ont leurs avantages/inconvénients, il suffit donc de choisir ce qui fait sens pour votre code logiquement. En outre, ne sont que rarement le peu de différence de vitesse jamais la cause d'un problème, utilisez donc ce qui fait sens pour la maintenabilité et la lisibilité)