retornando no meio de um bloco usando

196

Algo como:

using (IDisposable disposable = GetSomeDisposable())
{
    //.....
    //......
    return Stg();
}

Eu acredito que não é um lugar apropriado para uma declaração de retorno, é?

tafa
fonte

Respostas:

194

Como vários outros apontaram em geral, isso não é um problema.

O único caso que causará problemas é se você retornar no meio de uma instrução using e adicionalmente retornar a variável using. Mas, novamente, isso também causaria problemas, mesmo que você não voltasse e simplesmente mantivesse uma referência a uma variável.

using ( var x = new Something() ) { 
  // not a good idea
  return x;
}

Tão ruim

Something y;
using ( var x = new Something() ) {
  y = x;
}
JaredPar
fonte
1
Eu estava prestes a editar minha pergunta sobre o ponto que você mencionou. Obrigado.
21129
Por favor, ajude-me a entender por que isso é ruim. Gostaria de retornar um fluxo que estou usando em uma função auxiliar para outra função para processamento de imagem. Parece que o Stream será descartado se eu fizer isso?
John Shedletsky
3
@JohnShedletsky Nesse caso, sua chamada de função deve ser encerrada com o uso. Como usar (Stream x = FuncToReturnStream ()) {...} e não usar dentro de FuncToReturnStream.
Felix Keil #
@JohnShedletsky Tenho certeza de que é porque a returninstrução torna o fim do usingbloco inacessível por qualquer caminho de código. O final do usingbloco precisa ser executado para que o objeto possa ser descartado, se necessário.
precisa
147

Está perfeitamente bem.

Você aparentemente está pensando que

using (IDisposable disposable = GetSomeDisposable())
{
    //.....
    //......
    return Stg();
}

é traduzido cegamente para:

IDisposable disposable = GetSomeDisposable()
//.....
//......
return Stg();
disposable.Dispose();

O que, reconhecidamente, seria um problema e tornaria a usingafirmação inútil - e é por isso que não é isso que ela faz.

O compilador garante que o objeto seja descartado antes do controle sair do bloco - independentemente de como ele sai do bloco.

James Curran
fonte
7
Eu estava, aparentemente.
21129
Ótima resposta @James Curran! Mas isso me deixa curioso para o que é traduzido. Ou isso é apenas expressável em IL? (que eu nunca tentei ler antes).
Bart Bart
1
@ Bart - Eu penso nisso como avaliar a expressão de retorno em uma variável temporária, depois fazer o descarte e retornar a variável temporária.
Página
@James Curran. De cima para cá, apenas você explicou o que aconteceu em segundo plano. Muito Obrigado.
Sercan Timoçin 10/09/19
O @Bart provavelmente está traduzido para: tente {... seu código ...} finalmente {x.Dispose (); }
Bip901 em
94

É absolutamente bom - não há problema algum. Por que você acredita que está errado?

Uma declaração de uso é apenas açúcar sintático para um bloco try / finalmente, e como Grzenio diz que é bom retornar também de um bloco try.

A expressão de retorno será avaliada, o bloco final será executado e o método retornará.

Jon Skeet
fonte
5
A resposta de James Curran explica o que eu estava pensando.
21129
27

Isso funcionará perfeitamente bem, assim como retornar no meio da try{}finally{}

Grzenio
fonte
18

Isso é totalmente aceitável. A usando instrução garante que o objeto IDisposable seja descartado, não importa o quê.

Do MSDN :

A instrução using garante que Dispose seja chamado mesmo se uma exceção ocorrer enquanto você estiver chamando métodos no objeto. Você pode obter o mesmo resultado colocando o objeto dentro de um bloco try e depois chamando Dispose em um bloco final; de fato, é assim que a instrução using é traduzida pelo compilador.

mbillard
fonte
14

O código abaixo mostra como usingestá funcionando:

private class TestClass : IDisposable
{
   private readonly string id;

   public TestClass(string id)
   {
      Console.WriteLine("'{0}' is created.", id);
      this.id = id;
   }

   public void Dispose()
   {
      Console.WriteLine("'{0}' is disposed.", id);
   }

   public override string ToString()
   {
      return id;
   }
}

private static TestClass TestUsingClose()
{
   using (var t1 = new TestClass("t1"))
   {
      using (var t2 = new TestClass("t2"))
      {
         using (var t3 = new TestClass("t3"))
         {
            return new TestClass(String.Format("Created from {0}, {1}, {2}", t1, t2, t3));
         }
      }
   }
}

[TestMethod]
public void Test()
{
   Assert.AreEqual("Created from t1, t2, t3", TestUsingClose().ToString());
}

Resultado:

't1' é criado.
't2' é criado.
't3' é criado.
'Criado a partir de t1, t2, t3' é criado.
't3' é descartado.
't2' é descartado.
't1' é descartado.

Os descartados são chamados após a declaração de retorno, mas antes da saída da função.

Bertrand
fonte
1
Por favor, note que alguns C # objetos dispor de uma forma personalizada, por exemplo, os clientes do WCF é um usando declaração como retorno acima "não pode acessar objeto descartado"
OzBob
-4

Talvez não seja 100% verdade que isso seja aceitável ...

Se você estiver aninhando usos e retornando de dentro de um aninhado, talvez não seja seguro.

Tome isso como um exemplo:

using (var memoryStream = new MemoryStream())
{
    using (var textwriter = new StreamWriter(memoryStream))
    {
        using (var csv = new CsvWriter(textwriter))
        {
            //..write some stuff to the stream using the CsvWriter
            return memoryStream.ToArray();
        }
    }
}

Eu estava passando em um DataTable para ser produzido como CSV. Com o retorno no meio, ele estava gravando todas as linhas no fluxo, mas o csv emitido estava sempre faltando uma linha (ou várias, dependendo do tamanho do buffer). Isso me disse que algo não estava sendo fechado corretamente.

A maneira correta é garantir que todas as utilizações anteriores sejam descartadas corretamente:

using (var memoryStream = new MemoryStream())
{
    using (var textwriter = new StreamWriter(memoryStream))
    {
        using (var csv = new CsvWriter(textwriter))
        {
            //..write some stuff to the stream using the CsvWriter
        }
    }

    return memoryStream.ToArray();
}
Okay, certo
fonte