Pergunta

Eu tenho uma classe C# com um Dispose função através IDisposable.Destina-se a ser usado dentro de um using bloquear para que o recurso caro que ele manipula possa ser liberado imediatamente.

O problema é que ocorreu um bug quando uma exceção foi lançada antes Dispose foi chamado, e o programador negligenciou o uso using ou finally.

Em C++, nunca precisei me preocupar com isso.A chamada ao destruidor de uma classe seria inserida automaticamente no final do escopo do objeto.A única maneira de evitar que isso aconteça seria usar o operador new e segurar o objeto atrás de um ponteiro, mas isso exigia trabalho extra para o programador não é algo que ele faria por acidente, como esquecer de usar using.

Existe alguma maneira de using bloco a ser usado automaticamente em C#?

Muito obrigado.

ATUALIZAR:

Gostaria de explicar por que não estou aceitando as respostas do finalizador.Essas respostas são tecnicamente corretas por si só, mas não são destruidoras de estilo C++.

Aqui está o bug que encontrei, reduzido ao essencial ...

try
{
    PleaseDisposeMe a = new PleaseDisposeMe();
    throw new Exception();
    a.Dispose();
}
catch (Exception ex)
{
    Log(ex);
}

// This next call will throw a time-out exception unless the GC
// runs a.Dispose in time.
PleaseDisposeMe b = new PleaseDisposeMe();

Usando FXCop é uma excelente sugestão, mas se essa for minha única resposta, minha pergunta teria que se tornar um apelo ao pessoal de C# ou usar C++.Vinte instruções using aninhadas, alguém?

Foi útil?

Solução

Infelizmente não há como fazer isso diretamente no código.Se este for um problema interno, existem várias soluções de análise de código que podem detectar esse tipo de problema.Você já olhou para o FxCop?Eu acho que isso irá capturar essas situações e em todos os casos em que objetos IDisposable possam ficar pendurados.Se for um componente que pessoas estão usando fora da sua organização e você não pode exigir o FxCop, então a documentação é realmente seu único recurso :).

Editar:No caso dos finalizadores, isso não garante quando a finalização acontecerá.Portanto, esta pode ser uma solução para você, mas depende da situação.

Outras dicas

Onde trabalho, usamos as seguintes diretrizes:

  • Cada classe IDisposable deve tem um finalizador
  • Sempre que utilizar um objeto IDisposable, ele deve ser utilizado dentro de um bloco "using".A única exceção é se o objeto for membro de outra classe, caso em que a classe que o contém deve ser IDisposable e deve chamar o método 'Dispose' do membro em sua própria implementação de 'Dispose'.Isso significa que 'Dispose' nunca deve ser chamado pelo desenvolvedor, exceto dentro de outro método 'Dispose', eliminando o bug descrito na pergunta.
  • O código em cada Finalizador deve começar com um log de aviso/erro notificando-nos de que o finalizador foi chamado.Dessa forma, você tem uma chance extremamente boa de detectar bugs conforme descrito acima antes de liberar o código, além de poder ser uma dica para bugs que ocorrem em seu sistema.

Para facilitar nossa vida, também temos um método SafeDispose em nossa infraestrutura, que chama o método Dispose de seu argumento dentro de um bloco try-catch (com registro de erros), apenas por precaução (embora os métodos Dispose não devam lançar exceções ).

Veja também: Chris Lyonsugestões sobre IDisposable

Editar:@Quarrelsome:Uma coisa que você deve fazer é chamar GC.SuppressFinalize dentro de 'Dispose', para que se o objeto fosse descartado, ele não seria "redescartado".

Geralmente também é aconselhável segurar uma bandeira indicando se o objeto já foi descartado ou não.O seguinte padrão geralmente é muito bom:

class MyDisposable: IDisposable {
    public void Dispose() {
        lock(this) {
            if (disposed) {
                return;
            }

            disposed = true;
        }

        GC.SuppressFinalize(this);

        // Do actual disposing here ...
    }

    private bool disposed = false;
}

É claro que nem sempre o bloqueio é necessário, mas se você não tiver certeza se sua classe seria usada em um ambiente multithread ou não, é aconselhável mantê-lo.

@Quarrelsome

If será chamado quando o objeto for movido para fora do escopo e limpo pelo coletor de lixo.

Esta afirmação é enganosa e como a li incorreta:Não há absolutamente nenhuma garantia de quando o finalizador será chamado.Você está absolutamente certo de que o billpg deve implementar um finalizador;porém não será chamado automaticamente quando o objeto sair do escopo como ele deseja. Evidência, o primeiro ponto abaixo As operações de finalização têm as seguintes limitações.

Na verdade, a Microsoft concedeu uma concessão a Chris Sells para criar uma implementação do .NET que usasse contagem de referências em vez de coleta de lixo Link.Acontece que havia um considerável impacto no desempenho.

~ClassName()
{
}

EDITAR (negrito):

If será chamado quando o objeto for movido para fora do escopo e limpo pelo coletor de lixo no entanto, isso não é determinístico e não há garantia de que aconteça em um determinado momento.Isso é chamado de Finalizador.Todos os objetos com um finalizador são colocados em uma fila de finalização especial pelo coletor de lixo, onde o método finalize é invocado neles (portanto, é tecnicamente um impacto no desempenho declarar finalizadores vazios).

O padrão de descarte "aceito" de acordo com as Diretrizes da Estrutura é o seguinte com recursos não gerenciados:

    public class DisposableFinalisableClass : IDisposable
    {
        ~DisposableFinalisableClass()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                // tidy managed resources
            }

            // tidy unmanaged resources
        }
    }

Portanto, o que foi dito acima significa que se alguém ligar para Dispose, os recursos não gerenciados serão organizados.No entanto, no caso de alguém se esquecer de chamar Dispose ou de uma exceção que impeça Dispose de ser chamado, os recursos não gerenciados ainda serão arrumados, apenas um pouco mais tarde, quando o GC colocar suas mãos sujas nele (o que inclui o fechamento do aplicativo ou o encerramento inesperado ).

A melhor prática é usar um finalizador em sua aula e sempre usar using blocos.

Porém, não existe realmente um equivalente direto; os finalizadores se parecem com destruidores C, mas se comportam de maneira diferente.

Você deveria aninhar using blocos, é por isso que o layout do código C # usa como padrão colocá-los na mesma linha ...

using (SqlConnection con = new SqlConnection("DB con str") )
using (SqlCommand com = new SqlCommand( con, "sql query") )
{
    //now code is indented one level
    //technically we're nested twice
}

Quando você não está usando using você pode simplesmente fazer o que ele faz nos bastidores de qualquer maneira:

PleaseDisposeMe a;
try
{
    a = new PleaseDisposeMe();
    throw new Exception();
}
catch (Exception ex) { Log(ex); }  
finally {    
    //this always executes, even with the exception
    a.Dispose(); 
}

Com código gerenciado, o C# é muito bom em cuidar de sua própria memória, mesmo quando as coisas estão mal dispostas.Se você lida muito com recursos não gerenciados, não é tão forte.

Isso não é diferente de um programador que se esquece de usar excluir em C++, exceto que pelo menos aqui o coletor de lixo ainda irá alcançá-lo.

E você nunca precisará usar IDisposable se o único recurso que o preocupa for a memória.A estrutura cuidará disso sozinha.IDisposable é apenas para recursos não gerenciados, como conexões de banco de dados, fluxos de arquivos, soquetes e similares.

Um design melhor é fazer com que essa classe libere o recurso caro por conta própria, antes de ser descartado.

Por exemplo, se for uma conexão de banco de dados, conecte-se apenas quando necessário e libere imediatamente, muito antes de a classe real ser descartada.

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