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 Shoes, 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ł, że Shoe 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?

Author: GrahamS, 2009-03-19

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.

 34
Author: Grzenio,
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.

 19
Author: JaredPar,
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 .

 14
Author: Jordão,
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.

 3
Author: Adam,
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.

 3
Author: GrahamS,
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ł.

 3
Author: Joh,
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ę.

 2
Author: Henk Holterman,
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))}));
        }
    }
}
 1
Author: Darragh,
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