L'uso di yield per scorrere un lettore di dati potrebbe non chiudere la connessione?

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

  •  09-06-2019
  •  | 
  •  

Domanda

Ecco un codice di esempio per recuperare dati da un database utilizzando la parola chiave yield che ho trovato in qualche posto mentre cercavo su Google:

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();
        }
    }
}

Ho ragione nel pensare che in questo codice di esempio la connessione non verrebbe chiusa se non eseguiamo l'iterazione sull'intero lettore di dati?

Ecco un esempio che non chiuderebbe la connessione, se ho capito bene yield..

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

Per una connessione DB che potrebbe non essere catastrofica, suppongo che il GC alla fine la ripulirebbe, ma cosa accadrebbe se invece di una connessione fosse una risorsa più critica?

È stato utile?

Soluzione

L'Iterator sintetizzato dal compilatore implementa IDisposable, che foreach chiama quando si esce dal ciclo foreach.

Il metodo Dispose() dell'Iterator ripulirà le istruzioni using all'uscita anticipata.

Finché si utilizza l'iteratore in un ciclo foreach, nel blocco using() o si chiama il metodo Dispose() in qualche altro modo, verrà eseguita la pulizia dell'iteratore.

Altri suggerimenti

La connessione verrà chiusa automaticamente poiché la stai utilizzando all'interno del blocco "using".

Dal semplice test che ho provato, aku ha ragione, Dispose viene chiamato non appena esce il blocco foreach.

@Davide:Tuttavia lo stack di chiamate viene mantenuto tra una chiamata e l'altra, quindi la connessione non verrà chiusa perché alla chiamata successiva torneremmo all'istruzione successiva dopo il rendimento, che è il blocco while.

La mia comprensione è che quando l'iteratore viene eliminato, anche la connessione verrà eliminata con esso.Penso anche che Connection.Close non sarebbe necessario perché verrebbe gestito quando l'oggetto verrà eliminato a causa della clausola using.

Ecco un semplice programma che ho provato a testare il comportamento...

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;
    }
}

A giudicare da questa spiegazione tecnica, il codice non funzionerà come previsto, ma verrà interrotto sul secondo elemento, poiché la connessione era già chiusa al momento della restituzione del primo elemento.

@Joël Gauvreau:Sì, avrei dovuto continuare a leggere. Parte 3 di questa serie spiega che il compilatore aggiunge una gestione speciale per l'attivazione dei blocchi finalmente solo su vero FINE.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top