Wenn Sie yield zum Durchlaufen eines Datenlesers verwenden, wird die Verbindung möglicherweise nicht geschlossen?

StackOverflow https://stackoverflow.com/questions/47521

  •  09-06-2019
  •  | 
  •  

Frage

Hier ist ein Beispielcode zum Abrufen von Daten aus einer Datenbank mit dem Schlüsselwort yield, das ich beim Googeln an einigen Stellen gefunden habe:

public IEnumerable<object> ExecuteSelect(string commandText)
{
    using (IDbConnection connection = CreateConnection())
    {
        using (IDbCommand cmd = CreateCommand(commandText, connection))
        {
             connection.Open();
             using (IDbDataReader reader = cmd.ExecuteReader())
             {
                while(reader.Read())
                {
                    yield return reader["SomeField"];
                }
             }
             connection.Close();
        }
    }
}

Liege ich richtig, wenn ich denke, dass in diesem Beispielcode die Verbindung nicht geschlossen würde, wenn wir nicht über den gesamten Datenleser iterieren würden?

Hier ist ein Beispiel, das die Verbindung nicht schließen würde, wenn ich yield richtig verstehe.

foreach(object obj in ExecuteSelect(commandText))
{
  break;
}

Bei einer Datenbankverbindung, die möglicherweise nicht katastrophal ist, gehe ich davon aus, dass der GC sie irgendwann bereinigen würde, aber was wäre, wenn es sich anstelle einer Verbindung um eine kritischere Ressource handeln würde?

War es hilfreich?

Lösung

Der Iterator, den der Compiler synthetisiert, implementiert IDisposable, das foreach aufruft, wenn die foreach-Schleife beendet wird.

Die Dispose()-Methode des Iterators bereinigt die using-Anweisungen beim vorzeitigen Beenden.

Solange Sie den Iterator in einer foreach-Schleife, im using()-Block verwenden oder die Dispose()-Methode auf andere Weise aufrufen, erfolgt die Bereinigung des Iterators.

Andere Tipps

Die Verbindung wird automatisch geschlossen, da Sie sie im „using“-Block verwenden.

Nach dem einfachen Test, den ich versucht habe, hat aku recht, dispose wird aufgerufen, sobald der foreach-Block beendet wird.

@David:Der Aufrufstapel bleibt jedoch zwischen den Aufrufen erhalten, sodass die Verbindung nicht geschlossen wird, da wir beim nächsten Aufruf zur nächsten Anweisung nach dem Yield zurückkehren würden, also dem while-Block.

Meines Wissens nach wird beim Entsorgen des Iterators auch die Verbindung damit entsorgt.Ich denke auch, dass Connection.Close nicht benötigt würde, da es aufgrund der using-Klausel bei der Entsorgung des Objekts berücksichtigt würde.

Hier ist ein einfaches Programm, mit dem ich versucht habe, das Verhalten zu testen ...

class Program
{
    static void Main(string[] args)
    {
        foreach (int v in getValues())
        {
            Console.WriteLine(v);
        }
        Console.ReadKey();

        foreach (int v in getValues())
        {
            Console.WriteLine(v);
            break;
        }
        Console.ReadKey();
    }

    public static IEnumerable<int> getValues()
    {
        using (TestDisposable t = new TestDisposable())
        {
            for(int i = 0; i<10; i++)
                yield return t.GetValue();
        }
    }
}

public class TestDisposable : IDisposable
{
    private int value;

    public void Dispose()
    {
        Console.WriteLine("Disposed");
    }

    public int GetValue()
    {
        value += 1;
        return value;
    }
}

Gemessen an diese technische Erklärung, wird Ihr Code nicht wie erwartet funktionieren, sondern beim zweiten Element abbrechen, da die Verbindung bereits geschlossen war, als das erste Element zurückgegeben wurde.

@Joel Gauvreau:Ja, ich hätte weiterlesen sollen. Teil 3 In dieser Serie wird erklärt, dass der Compiler eine spezielle Behandlung für „finally“-Blöcke hinzufügt, die nur beim ausgelöst werden real Ende.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top