Tout en comprenant la façon dont les yield
œuvres de mots clés, je suis tombé sur link1 et lien2 sur StackOverflow qui préconise l'utilisation de yield return
tout itérer sur le DataReader et il convient à mes besoins aussi bien. Mais cela me fait me demander ce qui se passe, si j'utilise yield return
comme indiqué ci-dessous et si je n'itère pas à travers DataReader entier, la connexion DB restera-t-elle ouverte pour toujours?
IEnumerable<IDataRecord> GetRecords()
{
SqlConnection myConnection = new SqlConnection(@"...");
SqlCommand myCommand = new SqlCommand(@"...", myConnection);
myCommand.CommandType = System.Data.CommandType.Text;
myConnection.Open();
myReader = myCommand.ExecuteReader(CommandBehavior.CloseConnection);
try
{
while (myReader.Read())
{
yield return myReader;
}
}
finally
{
myReader.Close();
}
}
void AnotherMethod()
{
foreach(var rec in GetRecords())
{
i++;
System.Console.WriteLine(rec.GetString(1));
if (i == 5)
break;
}
}
J'ai essayé le même exemple dans un exemple d'application console et j'ai remarqué lors du débogage que le bloc finalement de GetRecords()
n'était pas exécuté. Comment puis-je assurer la fermeture de DB Connection? Existe-t-il un meilleur moyen que d'utiliser un yield
mot-clé? J'essaie de concevoir une classe personnalisée qui sera responsable de l'exécution de certains SQL et procédures stockées sur DB et retournera le résultat. Mais je ne veux pas retourner le DataReader à l'appelant. Je veux également m'assurer que la connexion sera fermée dans tous les scénarios.
Modifier la réponse à la réponse de Ben car il est incorrect de s'attendre à ce que les appelants utilisent correctement la méthode et en ce qui concerne la connexion DB, cela coûtera plus cher si la méthode est appelée plusieurs fois sans raison.
Merci Jakob et Ben pour l'explication détaillée.
Réponses:
Oui, vous serez confronté au problème que vous décrivez: jusqu'à ce que vous ayez fini d'itérer le résultat, vous garderez la connexion ouverte. Il y a deux approches générales auxquelles je peux penser pour traiter ceci:
Poussez, ne tirez pas
Actuellement, vous retournez une
IEnumerable<IDataRecord>
structure de données à partir de laquelle vous pouvez extraire. Au lieu de cela, vous pouvez changer de méthode pour diffuser ses résultats. Le plus simple serait de passer dans unAction<IDataRecord>
qui est appelé à chaque itération:Notez que étant donné que vous avez affaire à une collection d'éléments,
IObservable
/IObserver
peut être une structure de données légèrement plus appropriée, mais à moins que vous n'en ayez besoin, un simpleAction
est beaucoup plus simple.Évaluer avec enthousiasme
Une alternative consiste simplement à s'assurer que l'itération se termine entièrement avant de revenir.
Vous pouvez normalement le faire en mettant simplement les résultats dans une liste, puis en les renvoyant, mais dans ce cas, il y a la complication supplémentaire de chaque élément étant la même référence pour le lecteur. Vous avez donc besoin de quelque chose pour extraire le résultat dont vous avez besoin du lecteur:
la source
Votre
finally
bloc s'exécutera toujours.Lorsque vous utilisez
yield return
le compilateur, une nouvelle classe imbriquée est créée pour implémenter une machine à états.Cette classe contiendra tout le code des
finally
blocs en tant que méthodes distinctes. Il gardera une trace desfinally
blocs qui doivent être exécutés en fonction de l'état. Tous lesfinally
blocs nécessaires seront exécutés dans laDispose
méthode.Selon les spécifications du langage C #,
foreach (V v in x) embedded-statement
est étendu àainsi l'énumérateur sera supprimé même si vous quittez la boucle avec
break
oureturn
.Pour obtenir plus d'informations sur la mise en œuvre de l'itérateur, vous pouvez lire cet article de Jon Skeet
Éditer
Le problème avec une telle approche est que vous comptez sur les clients de votre classe pour utiliser correctement la méthode. Ils pourraient par exemple obtenir directement l'énumérateur et le parcourir en
while
boucle sans le supprimer.Vous devriez considérer l'une des solutions suggérées par @BenAaronson.
la source
var records = GetRecords(); var first = records.First(); var count = records.Count()
exécutera cette méthode deux fois, ouvrant et fermant la connexion et le lecteur à chaque fois. Le répéter deux fois fait la même chose. Vous pouvez également faire circuler le résultat pendant longtemps avant d'itérer réellement dessus, etc. Rien dans la signature de la méthode n'implique ce genre de comportementGetRecords
returnIEnumerable
, je m'attends à ce qu'elle charge les enregistrements en mémoire. D'un autre côté, si c'était une propriétéRecords
, je préfère supposer que c'est une sorte d'abstraction sur la base de données.Quel que soit le
yield
comportement spécifique , votre code contient des chemins d'exécution qui ne disposeront pas correctement de vos ressources. Et si votre deuxième ligne lève une exception ou votre troisième? Ou même votre quatrième? Vous aurez besoin d'une chaîne try / finally très complexe, ou vous pouvez utiliser desusing
blocs.Les gens ont remarqué que ce n'est pas intuitif, que les personnes qui appellent votre méthode peuvent ne pas savoir qu'énumérer le résultat deux fois appellera la méthode deux fois. Eh bien, bonne chance. C'est ainsi que la langue fonctionne. C'est le signal qui
IEnumerable<T>
envoie. Il y a une raison pour laquelle cela ne revient pasList<T>
ouT[]
. Les gens qui ne le savent pas doivent être éduqués, pas contournés.Visual Studio possède une fonctionnalité appelée analyse de code statique. Vous pouvez l'utiliser pour savoir si vous avez correctement supprimé les ressources.
la source
IEnumerable
pour faire toutes sortes de choses, telles que lever des exceptions totalement indépendantes ou écrire des fichiers de 5 Go sur le disque. Ce n'est pas parce que le langage le permet qu'il est acceptable de renvoyer unIEnumerable
qui le fait à partir d'une méthode qui ne donne aucune indication que cela se produira.IEnumerable<T>
est l'indication que ce sera deux fois énumérant deux appels résultat. Vous obtiendrez même des avertissements utiles dans vos outils si vous énumérez plusieurs fois un énumérable. Le point concernant la levée d'exceptions est également valable quel que soit le type de retour. Si cette méthode renvoyait un,List<T>
elle lève les mêmes exceptions.IEnumerable
émetteur de mise en garde. Mais je pense aussi qu'en tant que rédacteur de méthode, vous avez la responsabilité d'adhérer à ce que votre signature implique. La méthode est appeléeGetRecords
, donc quand elle revient, vous vous attendez à ce qu'elle ait, vous savez, obtenu les enregistrements . S'il était appeléGiveMeSomethingICanPullRecordsFrom
(ou, de façon plus réalisteGetRecordSource
ou similaire), un paresseuxIEnumerable
serait plus acceptable.File
,ReadLines
etReadAllLines
peut être facilement distingué et c'est une bonne chose. (Bien que cela soit davantage dû au fait qu'une surcharge de méthode ne peut pas différer uniquement par type de retour). Peut-être que ReadRecords et ReadAllRecords pourraient également convenir ici comme schéma de dénomination.Ce que vous avez fait semble mal, mais fonctionnera bien.
Vous devez utiliser un IEnumerator <> , car il hérite également d' IDisposable .
Mais comme vous utilisez un foreach, le compilateur utilise toujours un IDisposable pour générer un IEnumerator <> pour le foreach.
en fait, le foreach implique beaucoup de choses en interne.
Vérifiez /programming/13459447/do-i-need-to-consider-disposing-of-any-ienumerablet-i-use
la source