Jakiś sposób na szybkie sprawdzenie, że coś jest IDisposable i wymaga opakowania w using?

1

Dokumentacja MS bywa niekonsekwentna.

Bywa, że opis w doku jakiegoś typu wyraźnie krzyczy, że jest IDisposable i musi być using albo Dispose(), `RNGCryptoServiceProvider na ten przykład:

Important

This type implements the IDisposable interface. When you have finished using the type, you should dispose of it either directly or indirectly. To dispose of the type directly, call its Dispose method in a try/catch block. To dispose of it indirectly, use a language construct such as using (in C#) or Using (in Visual Basic). For more information, see the "Using an Object that Implements IDisposable" section in the IDisposable interface topic.

Więc przez długi czas szukałem po prostu w docu czy jest wielkie ogłoszenie że IDisposable i jeśli nie było, no to zakładałem, że nie jest IDisposable.

Błąd. Ciągle usuwam w kodzie przypadki użycia obiektów IDisposable bez Dispose'owania ich.

Przykład. HttpClient. Nigdzie w docu nie ma wielkiego ogłoszenia, że IDisposable. Na samej górze gdy podają, po czym to dziedziczy, też nie ma jawny sposób podane, że implementuje IDisposable, a tylko, że dziedziczy po HttpMessageInvoker. By się zorientować, że IDisposable, trzeba albo kliknąć w klasę bazową, albo znaleźć metodę Dispose w sekcji Methods.

Ale OK - HttpClient to zły przykład, przecież każdy wie, że to musi być IDisposable bo pewnie trzyma jakieś sockety i w ogóle to powinien żyć tyle, ile apka.

To co można powiedzieć o takiej klasie, jak HMACSHA256??

Nie wpadłbym na to, że jest IDisposable (co tam w ogóle trzyma??). I znowu nie wynika to w żaden widoczny na pierwszy rzut oka z dokumentacji. Oczywiście nie opakowałem tego w using.

Dziwię się, że MS nie zaznacza wyraźniej typów IDisposable. Moim zdaniem Visual Studio powinien dawać jakąś literkę D nad każdą zmienną, która jest typu IDisposable i nad każdym wywołaniem metody, która zwraca coś, co jest IDisposable. Za łatwo przeoczyć, że użyło się czegoś IDisposable i nie zorientowało się, że to coś jest IDisposable, a przecież nieDispose'owanie obiektów IDisposable to wyciek i bug.

No ale jest jak jest. Jakieś sprawdzone w boju sposoby na nieprzeaczanie takich rzeczy? W jaki sposób upewniacie się, że nie użyliście niechcący czegoś, co jest IDisposable bez zauważenia, że to jest IDisposable?

5
YetAnohterone napisał(a):

To co można powiedzieć o takiej klasie, jak HMACSHA256??

https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.hmacsha256?view=netframework-4.7.2#methods

Jest metoda Dispose? Jest nawet napisane gdzie jest implementacja

Tak samo możesz sprawdzić czy masz dispose w IDE

1

Wciskasz F12 w VS i możesz sobie prześledzić dziedziczenia:

MACSHA256 : HMAC -> HMAC : KeyedHashAlgorithm -> KeyedHashAlgorithm : HashAlgorithm -> HashAlgorithm : IDisposable, ICryptoTransform
2

Jeśli dobrze pamiętam to ReSharper z automatu pokazywał warningi jak było gdzieś IDisposable bez usinga.
Może jakiś roslynator też to potrafi? @1a2b3c4d5e

3

@some_ONE:

Może jakiś roslynator też to potrafi? @1a2b3c4d5e

Roslynator chyba nie, ale jest wbudowana rule do tego którą trzeba enablnąć

screenshot-20220112201913.png

Wchodząc w / Tworząc plik .editorconfig

screenshot-20220112201937.png

można ustawić

screenshot-20220112201946.png

lub bezpośrednio

# To learn more about .editorconfig see https://aka.ms/editorconfigdocs
root = true

# All files
[*]
indent_style = space
dotnet_diagnostic.CA2000.severity=error

# Xml files
[*.xml]
indent_size = 2

I wtedy

screenshot-20220112202022.png

screenshot-20220112202028.png

Przykład. HttpClient. Nigdzie w docu nie ma wielkiego ogłoszenia, że IDisposable. Na samej górze gdy podają, po czym to dziedziczy, też nie ma jawny sposób podane, że implementuje IDisposable, a tylko, że dziedziczy po HttpMessageInvoker. By się zorientować, że IDisposable, trzeba albo kliknąć w klasę bazową, albo znaleźć metodę Dispose w sekcji Methods.

Ale OK - HttpClient to zły przykład, przecież każdy wie, że to musi być IDisposable bo pewnie trzyma jakieś sockety i w ogóle to powinien żyć tyle, ile apka.

heh
screenshot-20220112201134.png

2
YetAnohterone napisał(a):

Za łatwo przeoczyć, że użyło się czegoś IDisposable i nie zorientowało się, że to coś jest IDisposable, a przecież nieDispose'owanie obiektów IDisposable to wyciek i bug.

No ale jest jak jest. Jakieś sprawdzone w boju sposoby na nieprzeaczanie takich rzeczy? W jaki sposób upewniacie się, że nie użyliście niechcący czegoś, co jest IDisposable bez zauważenia, że to jest IDisposable?

Jak czegoś nie zdispozujemy to zrobi za nas to GC, więc nie używałbym tutaj tak wielkich słów jak bug i wyciek. Po prostu pamięta się z doświadczenia najpopularniejsze klasy które implementuja IDisposable. A jakimiś bardziej egoztycznymi przypadkami nie ma coś się przejmować.

0

Ja mam "przyzwyczajenia" z Delphi, że jak zrobiliśmy "new" dla obiektu to potem trzeba "free".
Więc piszę w VS nazwę klasy, kropkę i wpisuje czy jest "dispose".

1
neves napisał(a):

Jak czegoś nie zdispozujemy to zrobi za nas to GC, więc nie używałbym tutaj tak wielkich słów jak bug i wyciek.

Hmm, StackOverflow ma inne zdanie na ten temat:

The .Net Garbage Collector calls the Object.Finalize method of an object on garbage collection. By default this does nothing and must be overidden if you want to free additional resources.

Dispose is NOT automatically called and must be explicity called if resources are to be released, such as within a 'using' or 'try finally' block

1

To jest wyrwane z kontekstu, GC nie wywołuje bezpośrednio Dispose to jest prawda, tylko wspomniany finalizator, natomiast te wszystkie typy implementują IDisposable według wzorca:

using System;

class BaseClassWithFinalizer : IDisposable
{
    // To detect redundant calls
    private bool _disposedValue;

    ~BaseClassWithFinalizer() => Dispose(false);

    // Public implementation of Dispose pattern callable by consumers.
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    // Protected implementation of Dispose pattern.
    protected virtual void Dispose(bool disposing)
    {
        if (!_disposedValue)
        {
            if (disposing)
            {
                // TODO: dispose managed state (managed objects)
            }

            // TODO: free unmanaged resources (unmanaged objects) and override finalizer
            // TODO: set large fields to null
            _disposedValue = true;
        }
    }
}

https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/implementing-dispose

Nie znam ani jednego typu (dostrczanego od Microsoftu) implementującego interfejs IDisposable który nie były zgodny z tym wzorcem.

1
neves napisał(a):

Nie znam ani jednego typu (dostrczanego od Microsoftu) implementującego interfejs IDisposable który nie były zgodny z tym wzorcem.

A ja nie znam ani jednego projektu korzystającego wyłącznie z typów dostarczanych przez Microsoft. :)
Poza tym jest chyba pewna różnica między ręcznym zwolnieniem zasobów w momencie, który uważamy za prawidłowy, a czekaniem aż jaśnie pan GC weźmie się do roboty.

0

Pewnie, że jest różnica i to spora i jak najbardziej to należy robić. Tylko nie warto się przejmować tym, że nie wiedzieliśmy o tym że na jakimś mało znanym typie też trzeba Dispose wywołać, że jednak ktoś jest za naszymi plecami (GC), kto ewentualnie poprawi nasze zaniechanie.

4
neves napisał(a):

Pewnie, że jest różnica i to spora i jak najbardziej to należy robić. Tylko nie warto się przejmować tym, że nie wiedzieliśmy o tym że na jakimś mało znanym typie też trzeba Dispose wywołać, że jednak ktoś jest za naszymi plecami (GC), kto ewentualnie poprawi nasze zaniechanie.

tylko zakładając prawidłową implementację finalizera, ale w praktyce to jeśli tylko korzystasz z jakichkolwiek niezarządzanych zasobów (połączeń sieciowych / do bazy / plików / natywnych obiektów / niezarządzanej pamięci) pośrednio czy bezpośrednio to Dispose trzeba wywoływać ręcznie bo zanim GC wkroczy do akcji możemy już wyczerpać pulę dostępnych socketów / pamięci / czegokolwiek. I kilka razy zdarzyło mi się poprawiać taki błąd że przez brakujący Dispose aplikacja się zawieszała / przestawała działać.
Rozwiązaniem większości problemów jest wspomniane wyżej włączenie zasady CA2000

@1a2b3c4d5e: HttpClienta nie powinno się tworzyć samemu, IHttpClientFactory rozwiązuje problem i klienci stworzeni przez fabrykę mogą być normalnie disposowani. Poza tym problem z wyczerpującą się pulą socketów dotyczy głównie starszych wersji .NET, od .NET Core 2.1 domyślnie używany jest nowy handler SocketsHttpHandler który ma rozwiązywać podobnie jak IHttpClientFactory problemy z DNS i socket exhaustion.
Z kolei SocketsHttpHandler miał problemy z małą prędkością przesyłu przy dużym pingu przez potwierdzanie pakietów, rozwiązany dopiero w .NET 6 https://devblogs.microsoft.com/dotnet/dotnet-6-networking-improvements/

0

Ale nawet wołając dispose jawnie w kodzie, pamięć nie jest zwalniana deterministycznie i jesteśmy skazani na łaskę GC :p. Tyle że dajemy mu znać, że ma zabierać te śmieci jak najszybciej.

1
urke napisał(a):

Ale nawet wołając dispose jawnie w kodzie, pamięć nie jest zwalniana deterministycznie i jesteśmy skazani na łaskę GC :p. Tyle że dajemy mu znać, że ma zabierać te śmieci jak najszybciej.

ale to pamięć zarządzana - tej nigdy nie braknie z powodu niezwolnionych obiektów bo GC na to teoretycznie nie pozwoli. Metody Dispose nawet nie ma obiektach w pełni managed

1 użytkowników online, w tym zalogowanych: 0, gości: 1