Jak można stwierdzić, czy odwołanie do obiektu IDisposable zostało usunięte?

86

Czy istnieje metoda lub inny lekki sposób sprawdzenia, czy odniesienie dotyczy usuniętego obiektu?

PS - To tylko ciekawostka (śpij dobrze, nie w kodzie produkcyjnym). Tak, wiem, że mogę złapać ObjectDisposedExceptionpróbę uzyskania dostępu do członka obiektu.

Neil C. Obremski
źródło
11
Nie wiem. Wydaje się dziwne, że nie ma bool IsDisposed { get; }deklaracji w sprawie System.IDisposable.
nicodemus
3
@ nicodemus13: DisposeMetoda nakazuje obiektowi zwolnienie wszelkich zasobów, które uzyskał, ale jeszcze nie zostały zwolnione. Jeśli obiekt nigdy nie przechowuje zasobów, jego Disposemetoda na ogół nie będzie musiała nic robić; jeśli typ deklaruje void IDisposable.Dispose() {};, w przeciwnym razie może zignorować IDisposablebez obciążenia na wystąpienie. IsDisposedNieruchomości, które spodziewano się stać prawdą następujące żadnego Disposepołączenia wymagałoby dodanie inaczej-niepotrzebnego Boolean flagę każdym przypadku wielu rodzajów, które w przeciwnym razie mogłyby zignorować Dispose.
supercat
1
Ale gdziekolwiek wywołujesz metodę na obiekcie, który implementuje IDisposable, jak możesz sprawdzić, czy została usunięta jako pierwsza? Zamiast zakładać, że tak nie jest i łapać wyjątek? A może w jakiś sposób masz tak zarządzać swoim życiem, aby zawsze wiedzieć, czy jest on usuwany, czy nie?
nicodemus13
3
@ nicodemus13: Generalnie nie powinno się używać obiektu, nie wiedząc, że nie został i nie zostanie usunięty, z wyjątkiem przypadków, gdy ktoś jest przygotowany do potraktowania usunięcia obiektu przez zewnętrzny kod jako sygnał do przerwania wszelkich oczekujących działań z nim . IsDisposedFlaga może pomóc zapobiec kod z marnowania czasu na czynności, które nie mogą być może się uda, ale nadal będą musiały obsłużyć wyjątki w przypadku obiekt zostanie umieszczony pomiędzy IsDisposedczeku i próbując je wykorzystać.
supercat
WeakReferencewydaje się mieć tutaj znaczenie. To nie jest dokładnie wykrywacz IDipose'd, ale mówi ci, czy to GC'd
Malachi

Odpowiedzi:

48

Nie - domyślna implementacja wzorca IDisposable go nie obsługuje

Dandikas
źródło
41

System.Windows.Forms.Controlma IsDisposedwłaściwość, która jest ustawiona na wartość true po Dispose()wywołaniu . W swoich własnych obiektach IDisposable możesz łatwo utworzyć podobną właściwość.

Ryan Lundy
źródło
OP szukał już podobnej właściwości na obiektach, których nie tworzy. Byłby to dobry pomysł w przypadku obiektów, które tworzymy, ale większość klas jednorazowych w .NET nie przestrzega tej konwencji. Odpowiedź Dandikasa jest poprawna.
krillgar
2
@krillgar, w pytaniu OP nie ma nic, co potwierdzałoby twoje stwierdzenie.
Ryan Lundy
18

Nie ma nic wbudowanego, co na to pozwoli. Konieczne byłoby ujawnienie właściwości logicznej IsDisposed, która odzwierciedla wewnętrzną usuniętą flagę.

public class SimpleCleanup : IDisposable
{
    private bool disposed = false;

    public bool IsDisposed
    {
       get
       {
          return disposed;
       }
    }

    public SimpleCleanup()
    {
        this.handle = /*...*/;
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
               // free only managed resources here
            }

            // free unmanaged resources here
            disposed = true;
        }
    }

    public void Dispose()
    {
        Dispose(true);
    }
}
Scott Dorman
źródło
BTW, jeśli zaczniesz używać tego wzorca, pomoże zdefiniować nowy interfejs ( IDisposablePluslub cokolwiek innego), który dziedziczy IDisposablei zawiera bool IsDisposed { get; }. Dzięki temu łatwo zorientujesz się, które z Twoich IDisposableobiektów są obsługiwane IsDisposed.
ToolmakerSteve
Nie sądzę, aby można było odziedziczyć interfejs ze względu na sposób działania C #. Umieszczenie interfejsu po dwukropku dziedziczy go. Spodziewam się, że z drugiej strony zaimplementuje interfejs.
Mojżesz
9

Jeśli to nie jest twoja klasa i nie zapewnia właściwości IsDisposed (lub czegoś podobnego - nazwa jest tylko konwencją), to nie masz możliwości dowiedzenia się.

Ale jeśli jest to Twoja klasa i postępujesz zgodnie z kanoniczną implementacją IDisposable , po prostu uwidocz pole _disposed lub _isDisposed jako właściwość i sprawdź to.

jop
źródło
2

Ta Disposemetoda jest wymagana do wykonania dowolnego czyszczenia, które będzie wymagane, zanim obiekt zostanie porzucony; jeśli czyszczenie nie jest wymagane, nie trzeba nic robić. Wymaganie od obiektu śledzenia tego, czy został usunięty, nawet jeśli Disposemetoda w przeciwnym razie nic by nie zrobiła, wymagałoby od wielu IDisposableobiektów dodania flagi z bardzo ograniczonymi korzyściami.

Pomocne mogłoby być IDisposableuwzględnienie dwóch właściwości - jednej, która wskazywałaby, czy obiekt wymaga usunięcia , i jednej, która wskazywałaby, że przedmiot nie stał się bezużyteczny w wyniku usunięcia. W przypadku obiektów, w których usuwanie faktycznie coś robi, obie wartości będą początkowo prawdziwe, a później staną się fałszywe Dispose. W przypadku obiektów, w których usuwanie nie wymaga żadnego czyszczenia, pierwsza metoda może zawsze zwracać fałsz, a druga zawsze prawda, bez konieczności przechowywania flagi w dowolnym miejscu. Nie wydaje mi się jednak, aby można je było teraz dodać do .NET.

supercat
źródło
IMHO, dwie flagi to przesada. Myślę, że lepiej jest trzymać się zwykłego paradygmatu, w którym po wywołaniu obiektu Dispose mamy jedną flagę. W przeciwnym razie dodasz złożoność, tylko po to, aby wiedzieć, że pewne obiekty „są nadal przydatne”, mimo że wywołano do nich polecenie Dispose. Nie warto iść tą drogą.
ToolmakerSteve
@ToolmakerSteve: Generalnie byłoby zero lub jedna flaga. W przypadku obiektów, które wymagają usunięcia, właściwości „wymaga usunięcia” i „jest użyteczne” dawałyby „prawda / prawda” przed usunięciem i „fałsz / fałsz” później, ale w przypadku obiektów, w przypadku których usunięcie byłoby niemożliwe, obie byłyby bezwarunkowe zwraca „fałsz / prawda”. Powiedzenie, że obiekt nadal wymaga utylizacji, gdy nigdy tego nie robi, lub że obiekt nie jest użyteczny, gdy zawsze jest, byłoby raczej nieprzyjemne. Przypuszczam, że innym podejściem byłoby użycie typu wyliczeniowego, aby wskazać, czy typ wymaga usunięcia, został usunięty, czy po prostu go to nie obchodzi.
supercat
@ToolmakerSteve: Myślę, że głównym powodem IDisposablebraku Disposedwłaściwości jest to, że byłoby to postrzegane jako dziwne mieć obiekty, do których wywołanie Disposenie ustawia takiej właściwości true, ale wymagałoby, aby obiekty śledziły, czy Disposezostało wywołane w przypadkach, gdy w przeciwnym razie nie mieliby powodu, aby się tym przejmować, spowodowałoby znaczne koszty i niewielkie korzyści.
supercat
1

Widzę, że to jest stare, ale nie widziałem odpowiedzi. Niektóre nie wszystkie obiekty jednorazowego użytku, takie jak DataSet, mają usunięte zdarzenie, które można dołączyć.

class DisposeSample : IDisposable
{
    DataSet myDataSet = new DataSet();
    private bool _isDisposed;

    public DisposeSample()
    {
        // attach dispose event for myDataSet
        myDataSet.Disposed += MyDataSet_Disposed;
    }

    private void MyDataSet_Disposed(object sender, EventArgs e)
    {
        //Event triggers when myDataSet is disposed
        _isDisposed = true; // set private bool variable as true 
    }


    public void Dispose()
    {
        if (!_isDisposed) // only dispose if has not been disposed;
            myDataSet?.Dispose(); // only dispose if myDataSet is not null;
    }
}
Mojżesz
źródło
Dobrze wiedzieć. W szczególności Disposedzdarzenie jest członkiem System.ComponentModel.IComponentinterfejsu.
ToolmakerSteve
-1

Lubię deklarować obiekty bez ich inicjowania, ale ustawiam ich domyślne wartości na Nothing. Następnie na końcu pętli piszę:

If anObject IsNot Nothing Then anObject.Dispose()

Oto pełna próbka:

Public Sub Example()
    Dim inputPdf As PdfReader = Nothing, inputDoc As Document = Nothing, outputWriter As PdfWriter = Nothing

    'code goes here that may or may not end up using all three objects, 
    ' such as when I see that there aren't enough pages in the pdf once I open  
    ' the pdfreader and then abort by jumping to my cleanup routine using a goto ..

GoodExit:
    If inputPdf IsNot Nothing Then inputPdf.Dispose()
    If inputDoc IsNot Nothing Then inputDoc.Dispose()
    If outputWriter IsNot Nothing Then outputWriter.Dispose()
End Sub

Świetnie sprawdza się to również w przypadku umieszczania głównych obiektów na szczycie procedury, używania ich wewnątrz Tryprocedury, a następnie umieszczania ich w Finallybloku:

Private Sub Test()
    Dim aForm As System.Windows.Forms.Form = Nothing
    Try
        Dim sName As String = aForm.Name  'null ref should occur
    Catch ex As Exception
        'got null exception, no doubt
    Finally
        'proper disposal occurs, error or no error, initialized or not..
        If aForm IsNot Nothing Then aForm.Dispose()
    End Try
End Sub
JeffreyDurham
źródło
6
@ LarsHöppner: Istota pytania jest niezależna od języka, a dobrzy programiści C # powinni prawdopodobnie znać przynajmniej tyle VB.NET, aby przeczytać powyższy kod (a programiści VB.NET powinni również nauczyć się C # wystarczająco dużo, aby czytać kod C #, który robić cokolwiek szczególnie egzotycznego).
supercat
3
Dlaczego miałbyś to wszystko zrobić zamiast używać Usinginstrukcji? To z pewnością istniało w 2013 roku, kiedy napisano tę odpowiedź.
Cody Grey
Naprawdę "GoodExit:" co to za rok 1983 dla GOTO? Przestań tego używać.
Mojżesz,
To nie odpowiada na pytanie. W szczególności, po inputPdfustawieniu wartości (innej niż Nothing), Twoja odpowiedź nie pozwala stwierdzić, czy inputPdfzostała usunięta. Możesz częściowo rozwiązać ten problem, ustawiając inputPdf = Nothingpo usunięciu. Jednak nie pomogłoby to żadnym innym zmiennym, które zostały wskazane na ten sam obiekt, co inputPdf. To znaczy, jeśli nie: inputPdf = New PdfReader, Dim pdf2 As PdfReader = inputPdf, inputPdf.Dispose, inputPdf = Nothing, to nadal będzie w żaden sposób wiedzieć, że pdf2jest umieszczony (jest to ten sam obiekt, jak inputPdf).
ToolmakerSteve