Usar o rendimento para iterar em um leitor de dados pode não fechar a conexão?

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

  •  09-06-2019
  •  | 
  •  

Pergunta

Aqui está um exemplo de código para recuperar dados de um banco de dados usando a palavra-chave yield que encontrei em alguns lugares enquanto pesquisava no 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();
        }
    }
}

Estou correto ao pensar que neste código de exemplo, a conexão não seria fechada se não iterássemos em todo o datareader?

Aqui está um exemplo que não fecharia a conexão, se bem entendi o rendimento.

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

Para uma conexão de banco de dados que pode não ser catastrófica, suponho que o GC a limparia eventualmente, mas e se, em vez de uma conexão, fosse um recurso mais crítico?

Foi útil?

Solução

O Iterador que o compilador sintetiza implementa IDisposable, que foreach chama quando o loop foreach é encerrado.

O método Dispose() do Iterator limpará as instruções using na saída antecipada.

Contanto que você use o iterador em um loop foreach, bloco using() ou chame o método Dispose() de alguma outra forma, a limpeza do Iterator acontecerá.

Outras dicas

A conexão será fechada automaticamente já que você a está usando dentro do bloco "using".

Pelo teste simples que tentei, aku está certo, o descarte é chamado assim que o bloco foreach sai.

@David:Porém a pilha de chamadas é mantida entre as chamadas, então a conexão não seria fechada porque na próxima chamada retornaríamos para a próxima instrução após o rendimento, que é o bloco while.

Meu entendimento é que quando o iterador for descartado, a conexão também será descartada com ele.Também acho que Connection.Close não seria necessário porque seria cuidado quando o objeto fosse descartado por causa da cláusula using.

Aqui está um programa simples que tentei testar o 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 julgar por esta explicação técnica, seu código não funcionará como esperado, mas abortará no segundo item, pois a conexão já estava fechada ao retornar o primeiro item.

@Joel Gauvreau:Sim, eu deveria ter continuado a ler. Parte 3 desta série explica que o compilador adiciona tratamento especial para que os blocos finalmente sejam acionados apenas no real fim.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top