Qual classe “possui” um recurso não gerenciado (e qual implementa IDisposable)?
-
11-12-2019 - |
Pergunta
Eu estou trabalhando em um projeto OSS para tornar o popular Biblioteca MediaInfo mais fácil de usar no .NET, mas esta questão é generalizável.
Se uma classe derivada D sempre instancia um objeto Ó ao chamar sua classe base BDconstrutor.O banco de dados define seu valor como aquele enviado ao seu construtor, mas o valor em si é declarado em BDclasse base de B:
- Quem possui" Ó (AKA mediaInfo no código abaixo)?
- No caso de uma aplicação .NET, qual delas deve implementar o IDisposable?Observação: Ó não é gerenciado ou pelo menos é uma instanciação de um objeto gerenciado agrupado em uma biblioteca não gerenciada, mas precisa de limpeza na forma de "MediaInfo.Close();".Não tenho certeza se isso conta como "não gerenciado".
Para ajudar a esclarecer, deixe-me usar o código real:
D deriva de BD:
// MediaFile is "D"
public sealed class MediaFile : GeneralStream
{
public MediaFile(string filePath)
: base(new MediaInfo(), 0) {
// mediaInfo is "O"
mediaInfo.Open(filePath);
}
}
BD define seu herdado Ó, derivado de B:
// GeneralStream is "DB"
public abstract class GeneralStream : StreamBaseClass
{
public GeneralStream(MediaInfo mediaInfo, int id) {
this.mediaInfo = mediaInfo; // declared in StreamBaseClass
// ...
}
}
B declara Ó:
// StreamBaseClass is "B"
public abstract class StreamBaseClass
{
protected MediaInfo mediaInfo; // "O" is declared
// ...
}
Solução
O objeto, que contém uma referência ao recurso, é o proprietário dele.
StreamBaseClass
tem a referência mediaInfo
e deveria implementar IDisposable
.A referência e o Dispose
método será automaticamente herdado pelas classes derivadas.
Outras dicas
Se uma classe C possui uma variável que é um não exposto variável local V que implementa IDisposable, então C deve ser IDisposable e o IDisposable de C deve descartar V.
Se uma classe D possui um recurso nativo N, então D deve ser IDisposable (que exclui N) e deveria também tem um destruidor finalizável que chama Dispose() para liberar N.
Se você seguir esse padrão, se você tiver um IDisposable, você deve sempre Dispose() usá-lo quando terminar e isso excluirá tudo na árvore de objetos;mas também se alguém se esquecer de você (leia:um colega, usuário de sua biblioteca etc) não vazará nenhum objeto porque o recurso nativo será limpo pelo finalizador de D.
A responsabilidade de um IDisposable
pertence ao objeto que cria isso, na ausência de qualquer acordo explícito em contrário.Acordos contrários são geralmente utilizados nos casos em que o criador de um recurso pode não ter ideia da vida útil do consumidor.Eu sugeriria que em muitos casos, quando um construtor ou método de fábrica está produzindo o que pode ser o último consumidor de um método passado IDisposable
, esse método deve aceitar um parâmetro indicando se ele deve aceitar a responsabilidade pela chamada Dispose
, ou então aceite um delegado de retorno de chamada que, se não for nulo, será invocado quando o consumidor não precisar mais do objeto.Se o criador do objeto sobreviver ao consumidor, ele poderá passar nulo;se o criador não tiver utilidade para o objeto depois que ele for entregue, ele poderá passar o objeto Dispose
método.Se o criador não souber se sobreviverá ao consumidor, ele poderá passar um método que determinará se o objeto ainda é necessário e chamar Dispose
se não.
No que diz respeito ao seu caso particular, construir um IDisposable
dentro de uma chamada de construtor encadeada é uma receita para vazamentos de recursos (já que não há como agrupar chamadas de construtor encadeadas em um bloco try-finalmente).Se você de alguma forma lidasse com isso com segurança (por exemplo,usando um método de fábrica em vez de um construtor encadeado, ou usando um [threadstatic]
hack), eu sugeriria que, como o criador do objeto (a classe derivada) saberá o tempo de vida do consumidor (a classe base), a propriedade e a responsabilidade pela limpeza deveriam permanecer com o criador do objeto.