Implementando IDisposable corretamente


145

Nas minhas aulas, implemento IDisposable da seguinte maneira:

public class User : IDisposable
{
    public int id { get; protected set; }
    public string name { get; protected set; }
    public string pass { get; protected set; }

    public User(int UserID)
    {
        id = UserID;
    }
    public User(string Username, string Password)
    {
        name = Username;
        pass = Password;
    }

    // Other functions go here...

    public void Dispose()
    {
        // Clear all property values that maybe have been set
        // when the class was instantiated
        id = 0;
        name = String.Empty;
        pass = String.Empty;
    }
}

No VS2012, minha análise de código diz para implementar IDisposable corretamente, mas não tenho certeza do que fiz de errado aqui.
O texto exato é o seguinte:

CA1063 Implementar IDisposable corretamente Forneça uma implementação substituível de Dispose (bool) em 'User' ou marque o tipo como lacrado. Uma chamada para Dispose (false) deve limpar apenas os recursos nativos. Uma chamada para Dispose (true) deve limpar os recursos gerenciados e nativos. stman User.cs 10

Para referência: CA1063: Implementar IDisposable corretamente

Eu li esta página, mas tenho medo de não entender o que precisa ser feito aqui.

Se alguém puder explicar em termos mais lânguidos qual é o problema e / ou como o IDisposable deve ser implementado, isso realmente ajudará!


1
Esse é todo o código Dispose?
precisa

42
Você deve implementar o método Dispose () para chamar o método Dispose () em qualquer membro da sua classe. Nenhum desses membros tem um. Portanto, você não deve implementar IDisposable. Redefinir os valores da propriedade é inútil.
Hans Passant

13
Você só precisa implementar IDispoablese tiver recursos não gerenciados para descarte (isso inclui recursos não gerenciados que são agrupados ( SqlConnection, FileStreametc.). Você não deve nem deve implementar IDisposablese tiver apenas recursos gerenciados como aqui. . um grande problema com a análise de código é muito bom em verificar bobo pequenas regras, mas não bom em verificação de erros conceituais.
jason

51
É muito perturbador para mim que algumas pessoas prefiram votar e ver essa pergunta encerrada do que tentar ajudar uma pessoa que claramente entendeu mal um conceito. Que pena.
Ortund

2
Portanto, não faça voto negativo, não vote, deixe a postagem em zero e feche a pergunta com um ponteiro útil.
precisa saber é o seguinte

Respostas:


113

Essa seria a implementação correta, embora eu não veja nada que você precise descartar no código publicado. Você só precisa implementar IDisposablequando:

  1. Você possui recursos não gerenciados
  2. Você está segurando referências de coisas que são elas mesmas descartáveis.

Nada no código que você postou precisa ser descartado.

public class User : IDisposable
{
    public int id { get; protected set; }
    public string name { get; protected set; }
    public string pass { get; protected set; }

    public User(int userID)
    {
        id = userID;
    }
    public User(string Username, string Password)
    {
        name = Username;
        pass = Password;
    }

    // Other functions go here...

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing) 
        {
            // free managed resources
        }
        // free native resources if there are any.
    }
}

2
Foi-me dito quando comecei a escrever em C # que é melhor usar using(){ }sempre que possível, mas para fazer isso, você precisa implementar o IDisposable; portanto, em geral, prefiro acessar uma classe usando usings, esp. se eu só precisa da classe em uma ou duas funções
Ortund

62
@ Ortund Você entendeu mal. É melhor usar um usingbloco quando a classe implementar IDisposable . Se você não precisa que uma classe seja descartável, não a implemente. Não serve para nada.
Daniel Mann

5
@DanielMann A semântica de um usingbloco tende a ser atraente além da IDisposableinterface sozinha. Imagino que tenha havido mais do que alguns abusos IDisposableapenas para fins de escopo.
Thomas Thomas

1
Como observação, se você quiser libertar recursos não gerenciados, inclua o Finalizer chamando Dispose (false), que permitirá que o GC chame o Finalizer ao fazer a coleta de lixo (no caso de Dispose ainda não ter sido chamado) e não seja gerenciado adequadamente gratuitamente Recursos.
Mariozski 17/05

4
Sem um finalizador na sua implementação, a chamada GC.SuppressFinalize(this);é inútil. Como o @mariozski apontou, um finalizador ajudaria a garantir que isso Disposeseja chamado se a classe não for usada dentro de um usingbloco.
Haymo Kutschbach

57

Primeiro de tudo, você não precisa de "clean up" strings e ints - eles vão ser realizado automaticamente pelo coletor de lixo. A única coisa que precisa ser limpa Disposesão recursos não gerenciados ou recursos gerenciados implementados IDisposable.

No entanto, supondo que este seja apenas um exercício de aprendizado, a maneira recomendada de implementar IDisposableé adicionar uma "trava de segurança" para garantir que nenhum recurso seja descartado duas vezes:

public void Dispose()
{
    Dispose(true);

    // Use SupressFinalize in case a subclass 
    // of this type implements a finalizer.
    GC.SuppressFinalize(this);   
}
protected virtual void Dispose(bool disposing)
{
    if (!_disposed)
    {
        if (disposing) 
        {
            // Clear all property values that maybe have been set
            // when the class was instantiated
            id = 0;
            name = String.Empty;
            pass = String.Empty;
        }

        // Indicate that the instance has been disposed.
        _disposed = true;   
    }
}

3
+1, ter uma bandeira para garantir que o código de limpeza é executada apenas uma vez é muito, muito melhor do que a definição de propriedades para nulo ou qualquer outra coisa (especialmente desde que interfere com a readonlysemântica)
Thomas

+1 por usar o código do usuário (mesmo que seja limpo automaticamente) para deixar claro o que está lá. Além disso, por não ser um marinheiro salgado e martelá-lo por cometer um pequeno erro enquanto aprende como muitos outros por aqui.
precisa saber é o seguinte

42

O exemplo a seguir mostra as melhores práticas gerais para implementar a IDisposableinterface. Referência

Lembre-se de que você precisa de um destruidor (finalizador) apenas se tiver recursos não gerenciados em sua classe. E se você adicionar um destruidor, suprimir Finalização em Dispor , caso contrário, os objetos residirão na memória por dois ciclos de lixo (Nota: Leia como a Finalização funciona ). Abaixo o exemplo elaborado acima.

public class DisposeExample
{
    // A base class that implements IDisposable. 
    // By implementing IDisposable, you are announcing that 
    // instances of this type allocate scarce resources. 
    public class MyResource: IDisposable
    {
        // Pointer to an external unmanaged resource. 
        private IntPtr handle;
        // Other managed resource this class uses. 
        private Component component = new Component();
        // Track whether Dispose has been called. 
        private bool disposed = false;

        // The class constructor. 
        public MyResource(IntPtr handle)
        {
            this.handle = handle;
        }

        // Implement IDisposable. 
        // Do not make this method virtual. 
        // A derived class should not be able to override this method. 
        public void Dispose()
        {
            Dispose(true);
            // This object will be cleaned up by the Dispose method. 
            // Therefore, you should call GC.SupressFinalize to 
            // take this object off the finalization queue 
            // and prevent finalization code for this object 
            // from executing a second time.
            GC.SuppressFinalize(this);
        }

        // Dispose(bool disposing) executes in two distinct scenarios. 
        // If disposing equals true, the method has been called directly 
        // or indirectly by a user's code. Managed and unmanaged resources 
        // can be disposed. 
        // If disposing equals false, the method has been called by the 
        // runtime from inside the finalizer and you should not reference 
        // other objects. Only unmanaged resources can be disposed. 
        protected virtual void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called. 
            if(!this.disposed)
            {
                // If disposing equals true, dispose all managed 
                // and unmanaged resources. 
                if(disposing)
                {
                    // Dispose managed resources.
                    component.Dispose();
                }

                // Call the appropriate methods to clean up 
                // unmanaged resources here. 
                // If disposing is false, 
                // only the following code is executed.
                CloseHandle(handle);
                handle = IntPtr.Zero;

                // Note disposing has been done.
                disposed = true;

            }
        }

        // Use interop to call the method necessary 
        // to clean up the unmanaged resource.
        [System.Runtime.InteropServices.DllImport("Kernel32")]
        private extern static Boolean CloseHandle(IntPtr handle);

        // Use C# destructor syntax for finalization code. 
        // This destructor will run only if the Dispose method 
        // does not get called. 
        // It gives your base class the opportunity to finalize. 
        // Do not provide destructors in types derived from this class.
        ~MyResource()
        {
            // Do not re-create Dispose clean-up code here. 
            // Calling Dispose(false) is optimal in terms of 
            // readability and maintainability.
            Dispose(false);
        }
    }
    public static void Main()
    {
        // Insert code here to create 
        // and use the MyResource object.
    }
}

14

IDisposableexiste para fornecer um meio para você limpar recursos não gerenciados que não serão limpos automaticamente pelo Garbage Collector.

Todos os recursos que você está "limpando" são recursos gerenciados e, como tal, seu Disposemétodo não está conseguindo nada. Sua classe não deve implementar IDisposablenada. O Garbage Collector cuidará de todos esses campos por conta própria.


1
Concordo com isso - existe a noção de descartar tudo quando na verdade não é necessário. Descarte deve ser usado apenas se você tiver recursos não gerenciados para limpar !!
Chandramouleswaran Ravichandra 25/03

4
Não estritamente verdadeiro, o método Dispose também permite dispor de "recursos gerenciados que implementam IDisposable"
Matt Wilko

@MattWilko Isso o torna uma maneira indireta de descartar recursos não gerenciados, porque permite que outro recurso descarte um recurso não gerenciado. Aqui não há nem mesmo uma referência indireta a um recurso não gerenciado por meio de outro recurso gerenciado.
Servy

@MattWilko Descarte será automaticamente chamado em qualquer recurso gerenciado que implementou IDesposable
Sharma panky

@pankysharma Não, não vai. Ele precisa ser chamado manualmente . Esse é o objetivo disso. Você não pode assumir que ele será chamado automaticamente, você sabe apenas que as pessoas devem chamá-lo manualmente, mas as pessoas cometem erros e esquecem.
Servy

14

Você precisa usar o padrão descartável como este:

private bool _disposed = false;

protected virtual void Dispose(bool disposing)
{
    if (!_disposed)
    {
        if (disposing)
        {
            // Dispose any managed objects
            // ...
        }

        // Now disposed of any unmanaged objects
        // ...

        _disposed = true;
    }
}

public void Dispose()
{
    Dispose(true);
    GC.SuppressFinalize(this);  
}

// Destructor
~YourClassName()
{
    Dispose(false);
}

1
não seria mais sensato ter uma chamada para GC.SuppressFinalize (this) no destruidor também? Caso contrário, o objeto em si seria recuperado no próximo GC
Sudhanshu Mishra 5/15/15

2
@dotnetguy: O destruidor de objetos é chamado quando o gc é executado. Então, ligar duas vezes não é possível. Consulte aqui: msdn.microsoft.com/en-us/library/ms244737.aspx
schoetbi

1
Então, agora estamos chamando qualquer parte do código clichê de "padrão"?
Chel

4
@rdhs Não, não somos. O MSDN declara que é um padrão "Dispose Pattern" aqui - msdn.microsoft.com/en-us/library/b1yfkh5e(v=vs.110).aspx .
Belogix

2
Nem a Microsoft nem a sua postagem afirmam claramente por que o padrão deve ser assim. Geralmente, nem é clichê, é apenas supérfluo - substituído por SafeHandle(e subtipos). No caso de recursos gerenciados, implementar o descarte adequado se torna muito mais simples; você pode reduzir o código para uma implementação simples do void Dispose()método
BatteryBackupUnit

10

Você não precisa fazer sua Userclasse, IDisposablepois a classe não adquire nenhum recurso não gerenciado (arquivo, conexão com o banco de dados etc.). Normalmente, marcamos as classes como IDisposablese tivessem pelo menos um IDisposablecampo ou propriedade. Ao implementar IDisposable, é melhor colocá-lo de acordo com o esquema típico da Microsoft:

public class User: IDisposable {
  ...
  protected virtual void Dispose(Boolean disposing) {
    if (disposing) {
      // There's no need to set zero empty values to fields 
      // id = 0;
      // name = String.Empty;
      // pass = String.Empty;

      //TODO: free your true resources here (usually IDisposable fields)
    }
  }

  public void Dispose() {
    Dispose(true);

    GC.SuppressFinalize(this);
  } 
}

Esse é geralmente o caso. Mas, por outro lado, a construção using abre a possibilidade de escrever algo semelhante aos ponteiros inteligentes do C ++, ou seja, um objeto que restaurará o estado anterior, não importando a saída de um bloco using. A única maneira que eu encontrei de fazer isso é fazer com que esse objeto implemente IDisposable. Parece que posso ignorar o aviso do compilador em um caso de uso tão marginal.
Papa Smurf

3

Idisposable é implementado sempre que você deseja uma coleta de lixo determinística (confirmada).

class Users : IDisposable
    {
        ~Users()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
            // This method will remove current object from garbage collector's queue 
            // and stop calling finilize method twice 
        }    

        public void Dispose(bool disposer)
        {
            if (disposer)
            {
                // dispose the managed objects
            }
            // dispose the unmanaged objects
        }
    }

Ao criar e usar a classe Users, use o bloco "using" para evitar chamar explicitamente o método de descarte:

using (Users _user = new Users())
            {
                // do user related work
            }

O fim do objeto Users criado pelo bloco using será descartado pelo método implícito de invocação de disposição.


2

Eu vejo muitos exemplos do padrão Microsoft Dispose, que é realmente um anti-padrão. Como muitos apontaram, o código na pergunta não requer IDisposable. Mas se você for implementá-lo, não use o padrão da Microsoft. Melhor resposta seria seguir as sugestões deste artigo:

https://www.codeproject.com/Articles/29534/IDisposable-What-Your-Mother-Never-Told-You-About

A única outra coisa que provavelmente seria útil é suprimir esse aviso de análise de código ... https://docs.microsoft.com/en-us/visualstudio/code-quality/in-source-suppression-overview?view=vs- 2017

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.