O 'uso' é apropriado em um contexto em que não há nada a eliminar?

9

Em C #, a usinginstrução é usada para dispor de maneira determinística os recursos sem aguardar o coletor de lixo. Por exemplo, pode ser usado para:

  • Descarte comandos ou conexões SQL,

  • Feche os fluxos, liberando a fonte subjacente como um arquivo,

  • Elementos GDI + gratuitos,

  • etc.

Percebi que isso usingé usado cada vez mais nos casos em que não há nada a ser descartado, mas onde é mais conveniente para o chamador escrever um usingbloco em vez de dois comandos separados.

Exemplos:

  • O MiniProfiler , escrito pela equipe Stack Overflow, usa usingpara indicar blocos no perfil:

    using (profiler.Step("Name goes here"))
    {
        this.DoSomethingUseful(i - 1);
    }
    

    Uma abordagem alternativa seria ter dois blocos:

    var p = profiler.Start("Name goes here");
    this.DoSomethingUseful(i - 1);
    profiler.Stop(p);
    

    Outra abordagem seria usar ações:

    profiler.Step("Name goes here", () => this.DoSomethingUseful(i - 1));
  • O ASP.NET MVC também selecionou os usingformulários:

    <% using (Html.BeginForm())
       { %>
           <label for="firstName">Name:</label>
           <%= Html.TextBox("name")%>
           <input type="submit" value="Save" />    
    <% } %>
    

Esse uso é apropriado? Como justificá-lo, uma vez que existem várias desvantagens:

  • Os iniciantes seriam perdidos, pois esse uso não corresponde ao explicado nos livros e nas especificações de idioma,

  • O código deve ser expressivo. Aqui, a expressividade sofre, pois o uso apropriado de usingé mostrar que, por trás, existe um recurso, como um fluxo, uma conexão de rede ou um banco de dados que deve ser liberado sem aguardar o coletor de lixo.

Arseni Mourzenko
fonte
2
A abordagem de ter duas instruções individuais sofre do mesmo problema que o gerenciamento de recursos ingênuo sem using: A última instrução (por exemplo profiler.Stop(p)) não é garantida para ser executada diante de exceções e fluxo de controle.
11
@ delnan: isso explica por que o MiniProfiler evitou a segunda abordagem. Mas o segundo deve ser evitado em todos os casos, pois é difícil escrever e manter.
Arseni Mourzenko
11
Relacionados: stackoverflow.com/questions/9472304
Robert Harvey
11
Relacionados: grabbagoft.blogspot.com/2007/06/...
Robert Harvey

Respostas:

7

Sua última afirmação - de que "o uso apropriado do uso é mostrar que, por trás, existe um recurso, como um fluxo, uma conexão de rede ou um banco de dados que deve ser liberado sem esperar o coletor de lixo" está incorreto, e a razão pela qual é fornecido na documentação da interface IDisposable: http://msdn.microsoft.com/en-us/library/system.idisposable.aspx

O principal uso dessa interface é liberar recursos não gerenciados.

Portanto, se sua classe usa recursos não gerenciados, não importa quando você pode ou não querer que o GC aconteça - não há nada a ver com o GC, pois os recursos não gerenciados não são gerados por GC ( https: // stackoverflow. com / questions / 3607213 / o que se entende por recursos gerenciados versus recursos não gerenciados na rede ).

Portanto, o objetivo de "usar" não é evitar a espera no GC, é forçar a liberação desses recursos não gerenciados agora , antes que a instância da classe saia do escopo e seja finalizada. Isso é importante por razões que devem ser óbvias - um recurso não gerenciado pode ter dependências de outros recursos não gerenciados e, se forem descartados (ou finalizados) na ordem errada, poderão ocorrer coisas ruins.

Portanto, se a classe que está sendo instanciada no bloco using usa recursos não gerenciados, a resposta é sim - é apropriado.

Observe que o IDisposable não é prescritivo, pois é apenas para liberar recursos não gerenciados - apenas que esse é seu objetivo principal . Ele pode ser o caso que o autor da classe tem alguma ação que eles querem impor acontecendo em um determinado momento, e implementar IDisposable pode ser uma maneira de conseguir que isso aconteça, mas se ou não isso é uma solução elegante é algo que pode só será respondido caso a caso.

Seja como for, o uso de "using" implica que a classe implemente IDisposable, para não violar a expressividade do código; deixa bem claro o que está acontecendo, de fato.

Maximus Minimus
fonte
"O principal uso dessa interface é liberar recursos não gerenciados." Eu digo que a documentação da Microsoft é péssima nesse ponto. Talvez seja assim que eles o usam na FCL, mas os desenvolvedores de aplicativos o usam há várias coisas há quase uma década - alguns por boas razões, outras por razões não tão boas. Mas citar esse pedaço de seu documento não responde à pergunta do OP.
precisa
11
Observe o meu segundo último parágrafo - o objetivo principal não precisa ser o único objetivo. O IDisposable pode ser implementado por qualquer motivo, e fornece uma interface conhecida com o comportamento e estilo de uso esperados; portanto, quando você o vê, sabe que há algo mais que precisa acontecer antes que a instância possa ficar fora do escopo. O ponto é: se implementar IDisposable, usar é apropriado; tudo o resto é apenas preâmbulo.
Maximus Minimus
11
Você está basicamente dizendo que o objetivo não é evitar esperar pelo GC, é evitar esperar pelo finalizador. Mas não vejo a diferença: o finalizador é chamado pelo GC.
svick
O GC opera de maneira não determinística - você não sabe quando será chamado. O GC também chama apenas o finalizador, mas não se descarta - o próprio recurso real está fora do controle do GC. Com o uso de (1), o objeto tem um escopo adequado e (2) sabe-se que o descarte ocorre em um momento conhecido - quando sai do escopo do bloco de uso, o recurso não gerenciado desaparece (ou, na pior das hipóteses, com a mão) para outra coisa que irá destruí-lo). Realmente, isso é apenas nitpicking; Leia a documentação, é tudo o que há, não precisa ser repetido.
Maximus Minimus
8

O uso de usingimplica a presença de um Dispose()método. Outros programadores assumirão que esse método existe no objeto. Consequentemente, se um objeto não é descartável, você não deve usá using-lo.

A clareza do código é fundamental. Omitir usingou implementar IDisposableno objeto.

O MiniProfiler parece estar usando usingcomo um mecanismo para "cercar" o código que está sendo criado. Há algum mérito nisso; presumivelmente, o MiniProfiler está chamando Dispose()para parar um cronômetro ou o cronômetro é parado quando o objeto MiniProfiler fica fora do escopo.

Em geral, você invocaria usingquando algum tipo de finalização precisasse ocorrer automaticamente. A documentação para html.BeginFormafirma que, quando o método é usado em uma usinginstrução, ele renderiza a </form>tag de fechamento no final do usingbloco.

Isso não significa necessariamente que ainda não é abuso.

Robert Harvey
fonte
4
Até agora, tão óbvio. A questão é: quando (se é que há) é apropriado implementar IDisposable/ Dispose()apesar de não ter nada a dispor no sentido que o OP está descrevendo?
2
Eu pensei que deixei isso claro; não um momento em que isso seja apropriado.
Robert Harvey
11
Meu ponto é, Disposeé necessário para usingfazer sentido em tudo (independentemente de se tratar de caso). Todos os exemplos do OP são implementados Dispose, não é? E no IIUC, a questão é se é correto que essas classes usem Dispose, em vez de algum outro método (como o Stop()MiniProfiler).
11
Sim, mas essa não é a questão. A questão é se é uma boa ideia fazê-lo.
2
@ delnan: Mais uma vez, pensei ter deixado isso claro. Se tornar mais clara a intenção do seu código, é uma boa ideia. Se não, não é.
Robert Harvey
1

usingO erro e a exceção são seguros. Ele garante Dispose()que será chamado independentemente dos erros cometidos pelo programador. Ele não interfere na captura ou no tratamento de exceções, mas o Dispose()método é executado recursivamente na pilha quando uma exceção é lançada.

Objetos que implementam, IDisposemas não têm nada para descartar quando são concluídos. São, na melhor das hipóteses, protegendo seu design no futuro. Para que você não precise refatorar seu código-fonte, quando, no futuro, eles precisarem se desfazer de algo.

Reactgular
fonte