Comece com essas classes simples ...
Digamos que eu tenha um conjunto simples de classes como este:
class Bus
{
Driver busDriver = new Driver();
}
class Driver
{
Shoe[] shoes = { new Shoe(), new Shoe() };
}
class Shoe
{
Shoelace lace = new Shoelace();
}
class Shoelace
{
bool tied = false;
}
A Bus
tem a Driver
, o Driver
tem dois Shoe
s, cada Shoe
um tem a Shoelace
. Tudo muito bobo.
Adicione um objeto IDisposable ao Shoelace
Mais tarde, decido que alguma operação no Shoelace
poderia ser multithread, então adiciono um EventWaitHandle
para os threads se comunicarem. Então Shoelace
agora fica assim:
class Shoelace
{
private AutoResetEvent waitHandle = new AutoResetEvent(false);
bool tied = false;
// ... other stuff ..
}
Implementar IDisposable no cadarço
Mas agora o FxCop da Microsoft irá reclamar: "Implemente IDisposable no 'Shoelace' porque cria membros dos seguintes tipos de IDisposable: 'EventWaitHandle'."
Ok, eu implementar IDisposable
em Shoelace
e minha classe pequeno puro torna-se essa bagunça horrível:
class Shoelace : IDisposable
{
private AutoResetEvent waitHandle = new AutoResetEvent(false);
bool tied = false;
private bool disposed = false;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~Shoelace()
{
Dispose(false);
}
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
if (waitHandle != null)
{
waitHandle.Close();
waitHandle = null;
}
}
// No unmanaged resources to release otherwise they'd go here.
}
disposed = true;
}
}
Ou (como apontado pelos comentaristas), como Shoelace
ele próprio não possui recursos não gerenciados, eu poderia usar a implementação de descarte mais simples sem precisar do Dispose(bool)
e Destructor:
class Shoelace : IDisposable
{
private AutoResetEvent waitHandle = new AutoResetEvent(false);
bool tied = false;
public void Dispose()
{
if (waitHandle != null)
{
waitHandle.Close();
waitHandle = null;
}
GC.SuppressFinalize(this);
}
}
Assista com horror como IDisposable se espalha
Certo, isso é fixo. Mas agora o FxCop irá reclamar que Shoe
cria um Shoelace
, assim também Shoe
deve ser IDisposable
.
E Driver
cria Shoe
assim Driver
deve ser IDisposable
. E Bus
cria Driver
assim Bus
deve ser IDisposable
e assim por diante.
De repente, minha pequena mudança Shoelace
está me causando muito trabalho e meu chefe está se perguntando por que preciso fazer o checkout Bus
para fazer uma alteração Shoelace
.
A questão
Como você evita essa propagação IDisposable
, mas ainda garante que seus objetos não gerenciados sejam descartados corretamente?
fonte
Respostas:
Você realmente não pode "impedir" que IDisposable se espalhe. Algumas classes precisam ser descartadas,
AutoResetEvent
e a maneira mais eficiente é fazê-lo noDispose()
método para evitar a sobrecarga dos finalizadores. Mas esse método deve ser chamado de alguma forma, exatamente como no seu exemplo, as classes que encapsulam ou contêm IDisposable precisam descartá-las; portanto, também devem ser descartáveis etc. A única maneira de evitá-lo é:using
padrão)Em alguns casos, o IDisposable pode ser ignorado porque suporta um caso opcional. Por exemplo, WaitHandle implementa IDisposable para oferecer suporte a um Mutex nomeado. Se um nome não estiver sendo usado, o método Dispose não fará nada. MemoryStream é outro exemplo, ele não usa recursos do sistema e sua implementação Dispose também não faz nada. Pensar cuidadosamente se um recurso não gerenciado está sendo usado ou não pode ser instrutivo. Assim, é possível examinar as fontes disponíveis para as bibliotecas .net ou usar um descompilador.
fonte
Em termos de correção, não é possível impedir a propagação de IDisposable por meio de um relacionamento de objeto se um objeto pai criar e possuir essencialmente um objeto filho que agora deve ser descartável. O FxCop está correto nessa situação e o pai deve ser IDisposable.
O que você pode fazer é evitar adicionar um IDisposable a uma classe folha na sua hierarquia de objetos. Nem sempre é uma tarefa fácil, mas é um exercício interessante. De uma perspectiva lógica, não há razão para que um ShoeLace precise ser descartável. Em vez de adicionar um WaitHandle aqui, também é possível adicionar uma associação entre um ShoeLace e um WaitHandle no ponto em que é usado. A maneira mais simples é através de uma instância do Dictionary.
Se você pode mover o WaitHandle para uma associação frouxa por meio de um mapa no ponto em que o WaitHandle é realmente usado, você pode quebrar essa cadeia.
fonte
Para impedir a
IDisposable
propagação, você deve tentar encapsular o uso de um objeto descartável dentro de um único método. Tente criar um designShoelace
diferente:O escopo do identificador de espera é limitado ao
Tie
método, e a classe não precisa ter um campo descartável e, portanto, não precisa ser descartável.Como o identificador de espera é um detalhe de implementação dentro
Shoelace
dele, não deve mudar de forma alguma sua interface pública, como adicionar uma nova interface em sua declaração. O que acontecerá quando você não precisar mais de um campo descartável, você removerá aIDisposable
declaração? Se você pensa naShoelace
abstração , percebe rapidamente que ela não deve ser poluída por dependências de infraestrutura, comoIDisposable
.IDisposable
deve ser reservado para classes cuja abstração encapsula um recurso que requer limpeza determinística; ou seja, para classes em que a disponibilidade é parte da abstração .fonte
AutoResetEvent
é usado para se comunicar entre diferentes segmentos que operam dentro da mesma classe, portanto, deve ser uma variável de membro. Você não pode limitar seu escopo a um método. (por exemplo, imagine que um thread simplesmente fique esperando por algum trabalho bloqueandowaitHandle.WaitOne()
. O thread principal chama oshoelace.Tie()
método, que apenas executawaitHandle.Set()
ae retorna imediatamente).É basicamente o que acontece quando você mistura Composição ou Agregação com classes descartáveis. Como mencionado, a primeira saída seria refatorar o waitHandle do cadarço.
Dito isto, você pode reduzir consideravelmente o padrão Descartável quando não possui recursos não gerenciados. (Ainda estou procurando uma referência oficial para isso.)
Mas você pode omitir o destruidor e GC.SuppressFinalize (this); e talvez limpe um pouco o vazio virtual Dispose (descarte booleano).
fonte
Curiosamente, se
Driver
é definido como acima:Então, quando
Shoe
é feitoIDisposable
, o FxCop (v1.36) não reclama queDriver
também deveria serIDisposable
.No entanto, se for definido assim:
então vai reclamar.
Suspeito que isso seja apenas uma limitação do FxCop, e não uma solução, porque na primeira versão as
Shoe
instâncias ainda estão sendo criadas peloDriver
e ainda precisam ser descartadas de alguma forma.fonte
Shoe
construtores,shoes
seria deixada em um estado difícil?Eu não acho que exista uma maneira técnica de impedir que o IDisposable se espalhe se você mantiver seu design tão firmemente acoplado. Deve-se então perguntar se o design está correto.
No seu exemplo, acho que faz sentido que o sapato seja o dono do cadarço e, talvez, o motorista deva ser o dono dos sapatos. No entanto, o ônibus não deve ser o dono do motorista. Normalmente, os motoristas de ônibus não seguem os ônibus até o ferro-velho :) No caso de motoristas e sapatos, os motoristas raramente fazem seus próprios sapatos, o que significa que eles realmente não os "possuem".
Um design alternativo pode ser:
Infelizmente, o novo design é mais complicado, pois exige classes extras para instanciar e descartar instâncias concretas de calçados e tênis, mas essa complicação é inerente ao problema que está sendo resolvido. O bom é que os ônibus não precisam mais ser descartáveis simplesmente com a finalidade de descartar os cadarços.
fonte
Que tal usar Inversão de Controle?
fonte