Introduction : La peur familière de tout développeur C#
\nImaginez la situation : vous écrivez une requête vers une base de données ou une collection en mémoire. Vous devez obtenir le premier élément qui satisfait une condition. Vous êtes sûr que cet élément existe obligatoirement. Ou pas ? C'est là que commence la danse : .First() ou .FirstOrDefault() ?
Cette question est un véritable classique sur Stack Overflow avec des milliers de vues. Et ce n'est pas pour rien. Le choix entre ces deux méthodes peut faire la différence entre un code propre et prévisible, et une InvalidOperationException soudaine en Production. Mettons fin une fois pour toutes aux doutes et clarifions quand utiliser chaque méthode.
L'essentiel en bref : Quelle est la différence ?
\nLes deux méthodes font presque la même chose : elles retournent le premier élément d'une séquence qui satisfait une condition (ou simplement le premier élément si aucune condition n'est spécifiée). Mais il y a une différence cruciale :
\n- \n
- .First() — si l'élément n'est pas trouvé, elle lève une exception
System.InvalidOperationException. \n - .FirstOrDefault() — si l'élément n'est pas trouvé, elle retourne la valeur par défaut du type (
nullpour les types référence,0pourint,falsepourbool, etc.). \n
Ça a l'air simple. Mais comme toujours, le diable se cache dans les détails.
\n\nQuand utiliser .First() : Quand "il doit y en avoir un, un point c'est tout"
\nUtilisez .First() lorsque votre logique métier garantit strictement que l'élément existe. S'il n'existe pas, c'est une situation exceptionnelle, une erreur dans les données ou la logique de l'application.
Exemple 1 : Recherche par identifiant unique
\nSi vous recherchez un utilisateur par sa clé primaire dans la base de données (par exemple, par son Id), et que vous êtes sûr que cet utilisateur existe, utilisez .First() :
public class UserService\n{\n private readonly AppDbContext _context;\n\n public UserService(AppDbContext context)\n {\n _context = context;\n }\n\n public User GetUserById(int userId)\n {\n // Nous nous attendons à ce que l'utilisateur avec cet ID existe.\n // Sinon, c'est un problème sérieux, il vaut mieux lever une exception.\n return _context.Users.First(u => u.Id == userId);\n }\n}\nDans ce cas, l'exception est un signal d'alarme. Elle dit : "Hé, les données sont corrompues, ou quelqu'un a passé un mauvais ID". Vous attraperez cette exception plus haut dans la pile et la traiterez comme une erreur critique.
\n\nExemple 2 : Premier élément après un tri (garanti)
\nSi vous triez une liste et prenez le premier élément, et que la liste contient forcément des éléments :
\nvar highestScore = players\n .OrderByDescending(p => p.Score)\n .First(); // Il ne peut pas y avoir 0 joueurs, nous l'avons vérifié avant\nSi vous avez préalablement vérifié que la collection n'est pas vide (par exemple, if (players.Any())), alors .First() est votre choix.
Quand utiliser .FirstOrDefault() : Quand l'élément peut ne pas exister
\nRègle d'or : Si l'absence de résultat est une situation normale et non une erreur, utilisez .FirstOrDefault().
Exemple 1 : Recherche optionnelle
\nImaginez que nous recherchions une commande par son numéro, mais que la commande peut ne pas encore avoir été créée :
\npublic Order FindOrderByNumber(string orderNumber)\n{\n // La commande peut ne pas exister. C'est normal.\n return _context.Orders\n .FirstOrDefault(o => o.OrderNumber == orderNumber);\n // Retournera null si la commande n'est pas trouvée.\n}\nAprès avoir appelé cette méthode, nous devons impérativement vérifier le résultat pour null :
var order = FindOrderByNumber("CMD-12345");\nif (order != null)\n{\n // Travailler avec la commande\n Console.WriteLine($"Commande trouvée : {order.Id}");\n}\nelse\n{\n Console.WriteLine("Commande non trouvée. Création d'une nouvelle.");