Jak zapobiec rozprzestrzenianiu się IDisposable na wszystkie klasy?
Zacznij od tych prostych klas...
Powiedzmy, że mam prosty zestaw klas takich jak:
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
mA Driver
, Driver
ma dwa Shoe
s, Każdy Shoe
mA Shoelace
. Wszystko bardzo głupie.
Dodaj IDisposable obiekt do Shoelace
Później postanawiam, że niektóre operacje na Shoelace
mogą być wielowątkowe, więc dodaję EventWaitHandle
, aby wątki mogły się z nimi komunikować. Więc Shoelace
teraz wygląda tak:
class Shoelace
{
private AutoResetEvent waitHandle = new AutoResetEvent(false);
bool tied = false;
// ... other stuff ..
}
Zaimplementuj IDisposable na Sznurowadła
Ale teraz Microsoft FxCop będzie narzekać: "zaimplementować IDisposable na 'Shoelace', ponieważ tworzy członków następujących IDisposable typów: 'EventWaitHandle'."
Ok, wprowadzam IDisposable
na Shoelace
i moja schludna klasa staje się strasznym bałaganem:
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;
}
}
Lub (jak podkreślają komentatorzy) ponieważ Shoelace
sama w sobie nie ma niezarządzanych zasobów, mogę użyć prostszej implementacji display bez potrzeby Dispose(bool)
i Destruktor:
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);
}
}
Oglądaj w horrorze jako IDisposable spreads
/ Align = "left" / Ale teraz FxCop będzie narzekał, żeShoe
tworzy Shoelace
, więc Shoe
też musi być IDisposable
.
I Driver
tworzy Shoe
więc Driver
musi być IDisposable
.
I Bus
tworzy Driver
więc Bus
musi być IDisposable
i tak dalej.
Nagle moja mała zmiana na Shoelace
sprawia, że mam dużo pracy i mój szef zastanawia się, dlaczego muszę dokonać zakupu Bus
, Aby dokonać zmiany na Shoelace
.
The Pytanie
Jak zapobiec rozprzestrzenianiu się IDisposable
, ale nadal upewnić się, że Twoje niezarządzane przedmioty są prawidłowo utylizowane?
8 answers
Naprawdę nie można "zapobiec" IDisposable przed rozprzestrzenianiem. Niektóre klasy muszą być usunięte, jak AutoResetEvent
, a najbardziej efektywnym sposobem jest to zrobić w metodzie Dispose()
, aby uniknąć napowietrznych finalizatorów. Ale ta metoda musi być w jakiś sposób wywołana, tak dokładnie jak w twoim przykładzie klasy, które enkapsulują lub zawierają IDisposable muszą je pozbywać się, więc muszą być również jednorazowe, itp. Jedynym sposobem na uniknięcie tego jest:
- unikaj używania klas IDisposable tam, gdzie to możliwe, lock lub czekać na wydarzenia w pojedynczych miejscach, trzymać drogie zasoby w jednym miejscu, itp
- twórz je tylko wtedy, gdy ich potrzebujesz i pozbywaj się ich zaraz po (wzór
using
)
W niektórych przypadkach IDisposable może być ignorowane, ponieważ obsługuje opcjonalny przypadek. Na przykład, WaitHandle implementuje IDisposable do obsługi nazwanego Mutex. Jeśli nazwa nie jest używana, metoda unieszkodliwiania nie robi nic. MemoryStream jest Innym przykładem, nie wykorzystuje zasobów systemowych i jego utylizacji implementacja również nic nie robi. Uważne myślenie o tym, czy zasób niezarządzany jest używany, czy nie, może być pouczające. Można więc sprawdzać dostępne źródła dla bibliotek. NET lub używać dekompilatora.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-12-29 03:02:44
Jeśli chodzi o poprawność, nie można zapobiec rozprzestrzenianiu się IDisposable poprzez relację obiektów, jeśli obiekt rodzic tworzy i zasadniczo posiada obiekt potomny, który musi być teraz jednorazowy. FxCop jest poprawny w tej sytuacji i rodzic musi być IDisposable.
Możesz uniknąć dodawania IDisposable do klasy leaf w hierarchii obiektów. Nie zawsze jest to łatwe zadanie, ale interesujące ćwiczenie. Z logicznego punktu widzenia nie ma powodu, aby Sznurowadła muszą być jednorazowe. Zamiast dodawać WaitHandle tutaj, jest to również możliwe, aby dodać skojarzenie między Sznurowadłem i WaitHandle w miejscu, w którym jest używany. Najprostszym sposobem jest wystąpienie słownika.
Jeśli możesz przenieść WaitHandle do luźnego związku za pomocą mapy w punkcie, w którym WaitHandle jest faktycznie używany, możesz przerwać ten łańcuch.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-18 07:42:42
Aby zapobiec rozprzestrzenianiu się IDisposable
, należy spróbować zamknąć użycie jednorazowego obiektu wewnątrz jednej metody. Spróbuj zaprojektować Shoelace
inaczej:
class Shoelace {
bool tied = false;
public void Tie() {
using (var waitHandle = new AutoResetEvent(false)) {
// you can even pass the disposable to other methods
OtherMethod(waitHandle);
// or hold it in a field (but FxCop will complain that your class is not disposable),
// as long as you take control of its lifecycle
_waitHandle = waitHandle;
OtherMethodThatUsesTheWaitHandleFromTheField();
}
}
}
Zakres rękojeści wait jest ograniczony do metody Tie
, A klasa nie musi mieć pola jednorazowego użytku, więc sama nie musi być jednorazowa.
Ponieważ uchwyt oczekiwania jest szczegółem implementacji wewnątrz Shoelace
, nie powinien zmieniać w żaden sposób swojego publicznego interfejsu, jak dodanie nowego interfejs w deklaracji. Co się stanie, gdy nie będzie już potrzebne pole jednorazowe, czy usuniesz deklarację IDisposable
? Jeśli myślisz o Shoelace
abstrakcja , szybko zdajesz sobie sprawę, że nie powinna być zanieczyszczana przez zależności infrastrukturalne, takie jak IDisposable
. IDisposable
powinny być zarezerwowane dla klas, których abstrakcja zawiera zasób, który wymaga deterministycznego oczyszczenia; tj. dla klas, w których dyspozycyjność jest częścią abstrakcji .
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-10 13:44:29
Wydaje się to bardzo podobne do kwestii projektowania wyższego poziomu, jak to często bywa, gdy "szybka naprawa" przekształca się w quagmire. Aby uzyskać więcej informacji na temat sposobów wyjścia, możesz znaleźć Ten wątek pomocny.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:15
Co ciekawe, jeśli Driver
jest zdefiniowane jak powyżej:
class Driver
{
Shoe[] shoes = { new Shoe(), new Shoe() };
}
Wtedy gdy Shoe
jest wykonane IDisposable
, FxCop (v1.36) nie skarży się, że Driver
powinno być również IDisposable
.
Jeśli jednak jest zdefiniowany tak:
class Driver
{
Shoe leftShoe = new Shoe();
Shoe rightShoe = new Shoe();
}
Wtedy będzie narzekać.
Podejrzewam, że jest to tylko ograniczenie FxCop, a nie rozwiązanie, ponieważ w pierwszej wersji instancje Shoe
są nadal tworzone przez Driver
i nadal muszą być jakoś usunięte.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-26 02:36:33
Myślę, że nie ma technicznego sposobu, aby zapobiec rozprzestrzenianiu się IDisposable, jeśli utrzymujesz swój projekt tak ściśle powiązany. Należy wtedy zastanawiać się, czy projekt jest właściwy.
W twoim przykładzie, myślę, że to ma sens, aby But był właścicielem sznurowadła, a może kierowca powinien mieć swoje buty. Jednak autobus nie powinien być właścicielem kierowcy. Zazwyczaj kierowcy autobusów nie jeżdżą autobusami na złomowisko :) w przypadku kierowców i butów kierowcy rzadko robią własne buty, czyli tak naprawdę ich nie "posiadają".
Alternatywnym projektem może być:
class Bus
{
IDriver busDriver = null;
public void SetDriver(IDriver d) { busDriver = d; }
}
class Driver : IDriver
{
IShoePair shoes = null;
public void PutShoesOn(IShoePair p) { shoes = p; }
}
class ShoePairWithDisposableLaces : IShoePair, IDisposable
{
Shoelace lace = new Shoelace();
}
class Shoelace : IDisposable
{
...
}
Nowy projekt jest niestety bardziej skomplikowany, ponieważ wymaga dodatkowych klas do tworzenia instancji i usuwania konkretnych przypadków butów i kierowców, ale ta komplikacja jest nieodłączna rozwiązywanemu problemowi. Dobrą rzeczą jest to, że autobusy nie muszą być już jednorazowe tylko w celu pozbycia się sznurowadeł.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-06 20:04:12
To w zasadzie dzieje się, gdy mieszasz kompozycję lub agregację z klasami jednorazowymi. Jak wspomniano, pierwszym wyjściem będzie refaktoryzacja waitHandle z sznurowadła.
Powiedziawszy to, możesz rozebrać Jednorazowy wzór znacznie, gdy nie masz niezarządzanych zasobów. (Nadal szukam oficjalnego odniesienia do tego.)
Ale można pominąć Destruktor i GC.SuppressFinalize (this); a może czyszczenie wirtualnej pustki (bool pozbywanie się) trochę.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-19 11:55:54
Jak o użyciu inwersji sterowania?
class Bus
{
private Driver busDriver;
public Bus(Driver busDriver)
{
this.busDriver = busDriver;
}
}
class Driver
{
private Shoe[] shoes;
public Driver(Shoe[] shoes)
{
this.shoes = shoes;
}
}
class Shoe
{
private Shoelace lace;
public Shoe(Shoelace lace)
{
this.lace = lace;
}
}
class Shoelace
{
bool tied;
private AutoResetEvent waitHandle;
public Shoelace(bool tied, AutoResetEvent waitHandle)
{
this.tied = tied;
this.waitHandle = waitHandle;
}
}
class Program
{
static void Main(string[] args)
{
using (var leftShoeWaitHandle = new AutoResetEvent(false))
using (var rightShoeWaitHandle = new AutoResetEvent(false))
{
var bus = new Bus(new Driver(new[] {new Shoe(new Shoelace(false, leftShoeWaitHandle)),new Shoe(new Shoelace(false, rightShoeWaitHandle))}));
}
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-02 11:34:55