Während yield
ich verstand, wie ein Schlüsselwort funktioniert, bin ich bei StackOverflow auf Link1 und Link2 gestoßen, die die Verwendung von befürworten, yield return
während sie über den DataReader iterieren, und die auch meinen Anforderungen entsprechen. Aber ich frage mich, was passiert, wenn ich yield return
wie unten gezeigt verwende und nicht durch den gesamten DataReader iteriere. Bleibt die DB-Verbindung für immer offen?
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;
}
}
Ich habe das gleiche Beispiel in einer Beispiel-Konsolen-App ausprobiert und beim Debuggen festgestellt, dass der finally-Block von GetRecords()
nicht ausgeführt wird. Wie kann ich dann die Schließung der DB-Verbindung sicherstellen? Gibt es einen besseren Weg als das Verwenden von yield
Schlüsselwörtern? Ich versuche, eine benutzerdefinierte Klasse zu entwerfen, die für die Ausführung ausgewählter SQL-Anweisungen und gespeicherter Prozeduren in der Datenbank verantwortlich ist und das Ergebnis zurückgibt. Ich möchte den DataReader aber nicht an den Aufrufer zurückgeben. Außerdem möchte ich sicherstellen, dass die Verbindung in allen Szenarien geschlossen wird.
Bearbeiten Die Antwort auf Bens Antwort wurde geändert, da nicht erwartet werden kann, dass Methodenaufrufer die Methode korrekt verwenden. In Bezug auf die DB-Verbindung ist es teurer, wenn die Methode ohne Grund mehrmals aufgerufen wird.
Danke Jakob und Ben für die ausführliche Erklärung.
Antworten:
Ja, Sie werden mit dem von Ihnen beschriebenen Problem konfrontiert: Bis Sie das Ergebnis durchlaufen haben, bleibt die Verbindung geöffnet. Ich kann mir zwei allgemeine Ansätze vorstellen, um damit umzugehen:
Schieben, nicht ziehen
Derzeit geben Sie
IEnumerable<IDataRecord>
eine Datenstruktur zurück, aus der Sie Daten abrufen können. Stattdessen können Sie Ihre Methode wechseln, um die Ergebnisse zu veröffentlichen. Der einfachste Weg wäre, eine zu übergeben,Action<IDataRecord>
die bei jeder Iteration aufgerufen wird:Beachten Sie, dass gegebenen Sie mit einer Sammlung von Gegenständen zu tun,
IObservable
/IObserver
möglicherweise eine etwas geeignete Datenstruktur sein, aber wenn Sie es brauchen, ein einfachesAction
ist viel einfacher.Eifrig auswerten
Eine Alternative besteht nur darin, sicherzustellen, dass die Iteration vollständig abgeschlossen ist, bevor Sie zurückkehren.
Normalerweise können Sie dies tun, indem Sie die Ergebnisse in eine Liste einfügen und diese dann zurückgeben. In diesem Fall ist es jedoch zusätzlich kompliziert, dass jedes Element dieselbe Referenz für den Leser darstellt. Sie benötigen also etwas, um das gewünschte Ergebnis aus dem Reader zu extrahieren:
quelle
Ihr
finally
Block wird immer ausgeführt.Wenn Sie
yield return
den Compiler verwenden, wird eine neue verschachtelte Klasse erstellt, um einen Zustandsautomaten zu implementieren.Diese Klasse enthält den gesamten Code aus
finally
Blöcken als separate Methoden. Es verfolgt diefinally
Blöcke, die abhängig vom Status ausgeführt werden müssen. Alle notwendigenfinally
Blöcke werden inDispose
Methode ausgeführt.Laut C # -Sprachenspezifikation
foreach (V v in x) embedded-statement
wird auf erweitertDer Enumerator wird also verworfen, auch wenn Sie die Schleife mit
break
oder verlassenreturn
.Weitere Informationen zur Iterator-Implementierung finden Sie in diesem Artikel von Jon Skeet
Bearbeiten
Das Problem bei einem solchen Ansatz ist, dass Sie sich auf die Clients Ihrer Klasse verlassen, um die Methode korrekt zu verwenden. Sie könnten beispielsweise den Enumerator direkt abrufen und ihn in einer
while
Schleife durchlaufen, ohne ihn zu entsorgen.Sie sollten eine der von @BenAaronson vorgeschlagenen Lösungen in Betracht ziehen.
quelle
var records = GetRecords(); var first = records.First(); var count = records.Count()
Diese Methode wird tatsächlich zweimal ausgeführt, wobei die Verbindung und der Reader jedes Mal geöffnet und geschlossen werden. Das zweimalige Durchlaufen bewirkt dasselbe. Sie können das Ergebnis auch lange weitergeben, bevor Sie es tatsächlich durchlaufen usw. Nichts in der Methodensignatur deutet auf diese Art von Verhalten hinGetRecords
returningIEnumerable
sehe, erwarte ich, dass sie die Datensätze in den Speicher lädt. Wenn es sich andererseits um eine Eigenschaft handeltRecords
, würde ich eher annehmen , dass es sich um eine Art Abstraktion über eine Datenbank handelt.Ungeachtet des spezifischen
yield
Verhaltens enthält Ihr Code Ausführungspfade, über die Ihre Ressourcen nicht richtig verteilt werden. Was ist, wenn Ihre zweite Zeile eine Ausnahme auslöst oder Ihre dritte? Oder sogar dein vierter? Sie benötigen eine sehr komplexe try / finally-Kette, oder Sie könnenusing
Blöcke verwenden.Die Leute haben bemerkt, dass dies nicht intuitiv ist und dass die Leute, die Ihre Methode aufrufen, möglicherweise nicht wissen, dass eine zweimalige Aufzählung des Ergebnisses die Methode zweimal aufruft. Pech gehabt. So funktioniert die Sprache. Das ist das Signal, das
IEnumerable<T>
sendet. Es gibt einen Grund, warum dies nicht zurückkommtList<T>
oderT[]
. Leute, die das nicht wissen, müssen erzogen und nicht umgangen werden.Visual Studio verfügt über eine Funktion namens statische Code-Analyse. Sie können damit feststellen, ob Sie Ressourcen ordnungsgemäß entsorgt haben.
quelle
IEnumerable
es, alle möglichen Dinge durchlaufen zu lassen, z. B. völlig unabhängige Ausnahmen auszulösen oder 5-GB-Dateien auf die Festplatte zu schreiben. Nur weil die Sprache dies zulässt, bedeutet dies nicht, dass es akzeptabel ist, eine FunktionIEnumerable
von einer Methode zurückzugeben, die keinen Hinweis darauf gibt, dass dies passieren wird.IEnumerable<T>
ist der Hinweis darauf , dass es zweimal Aufzählen in zwei Anrufen führen. Sie erhalten sogar hilfreiche Warnungen in Ihren Tools, wenn Sie eine Aufzählung mehrmals durchführen. Der Punkt zum Auslösen von Ausnahmen ist unabhängig vom Rückgabetyp gleichermaßen gültig. Wenn diese Methode aList<T>
zurückgibt, werden die gleichen Ausnahmen ausgelöst.IEnumerable
Einschränkung bietet . Aber ich denke auch, dass Sie als Methodenschreiber die Verantwortung haben, sich an das zu halten, was Ihre Signatur impliziert. Die Methode wird aufgerufenGetRecords
. Wenn sie zurückgegeben wird, haben Sie, wie Sie wissen, die Datensätze erhalten . Wenn es heißtGiveMeSomethingICanPullRecordsFrom
(oder realistischerGetRecordSource
oder ähnlicher), dann wäre ein faulerIEnumerable
akzeptabler.File
,ReadLines
undReadAllLines
leicht auseinander gesagt werden kann , und das ist eine gute Sache. (Obwohl dies eher auf die Tatsache zurückzuführen ist, dass sich eine Methodenüberladung nicht nur nach dem Rückgabetyp unterscheiden kann). Vielleicht passen ReadRecords und ReadAllRecords auch hier als Namensschema.Was du getan hast, scheint falsch zu sein, wird aber gut funktionieren.
Sie sollten einen IEnumerator <> verwenden , da dieser auch IDisposable erbt .
Da Sie jedoch foreach verwenden, verwendet der Compiler weiterhin IDisposable , um einen IEnumerator <> für foreach zu generieren .
Tatsächlich beinhaltet der foreach eine Menge interner Dinge.
Überprüfen Sie /programming/13459447/do-i-need-to-consider-disposing-of-any-ienumerablet-i-use
quelle