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.


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



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.


"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.


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 há 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.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.