Czy „używanie” jest odpowiednie w kontekście, w którym nie ma nic do usunięcia?

9

W języku C # usinginstrukcja służy do deterministycznego usuwania zasobów bez oczekiwania na moduł odśmiecania. Na przykład można go użyć do:

  • Usuń polecenia SQL lub połączenia,

  • Zamknij strumienie, uwalniając źródło źródłowe jak plik,

  • Darmowe elementy GDI +,

  • itp.

Zauważyłem, że usingjest coraz częściej stosowany w przypadkach, w których nie ma nic do pozbycia się, ale tam, gdzie osoba dzwoniąca jest wygodniej napisać usingblok niż dwa osobne polecenia.

Przykłady:

  • MiniProfiler , napisany przez zespół Stack Overflow, używa usingdo oznaczania bloków do profilowania:

    using (profiler.Step("Name goes here"))
    {
        this.DoSomethingUseful(i - 1);
    }

    Jednym alternatywnym podejściem byłoby posiadanie dwóch bloków:

    var p = profiler.Start("Name goes here");
    this.DoSomethingUseful(i - 1);
    profiler.Stop(p);

    Innym podejściem byłoby użycie akcji:

    profiler.Step("Name goes here", () => this.DoSomethingUseful(i - 1));
  • Program ASP.NET MVC wybrał również usingformularze:

    <% using (Html.BeginForm())
       { %>
           <label for="firstName">Name:</label>
           <%= Html.TextBox("name")%>
           <input type="submit" value="Save" />    
    <% } %>

Czy takie użycie jest właściwe? Jak to uzasadnić, biorąc pod uwagę, że istnieje kilka wad:

  • Początkujący byliby zagubieni, ponieważ takie użycie nie odpowiada temu, które wyjaśniono w książkach i specyfikacji językowej,

  • Kod powinien być wyrazisty. Tutaj cierpi ekspresja, ponieważ właściwe użycie usingto pokazanie, że z tyłu znajduje się zasób, taki jak strumień, połączenie sieciowe lub baza danych, którą należy zwolnić bez czekania na śmieciarz.

Arseni Mourzenko
źródło
2
Podejście polegające na posiadaniu dwóch pojedynczych instrukcji ma ten sam problem co naiwne zarządzanie zasobami bez using: Ta ostatnia instrukcja (np. profiler.Stop(p)) Nie jest gwarantowana, że ​​zostanie wykonana w obliczu wyjątków i przepływu kontroli.
1
@ delnan: wyjaśnia, dlaczego MiniProfiler unikał drugiego podejścia. Ale drugiego należy unikać we wszystkich przypadkach, ponieważ trudno jest pisać i utrzymywać.
Arseni Mourzenko
1
Powiązane: stackoverflow.com/questions/9472304
Robert Harvey
1
Powiązane: grabbagoft.blogspot.com/2007/06/…
Robert Harvey

Odpowiedzi:

7

Twoje ostatnie stwierdzenie - „właściwe użycie za pomocą to pokazanie, że z tyłu znajduje się zasób, taki jak strumień, połączenie sieciowe lub baza danych, które powinny zostać zwolnione bez czekania na śmietnik” jest niepoprawne, i powód jest podane w dokumentacji interfejsu IDisposable: http://msdn.microsoft.com/en-us/library/system.idisposable.aspx

Głównym zastosowaniem tego interfejsu jest zwolnienie niezarządzanych zasobów.

Tak więc, jeśli twoja klasa korzysta z niezarządzanych zasobów, nie ma znaczenia, kiedy możesz lub nie chcesz, aby GC się wydarzyło - nie ma to nic wspólnego z GC, ponieważ niezarządzane zasoby nie są GC'owane ( https: // stackoverflow. com / pytania / 3607213 / what-is-mean-by-managed-vs.-unmanaged-resources-in-net ).

Tak więc celem „używania” nie jest uniknięcie czekania na GC, ale wymuszenie zwolnienia tych niezarządzanych zasobów teraz , zanim instancja klasy znajdzie się poza zasięgiem i zostanie wywołana jej finalizacja. Jest to ważne z powodów, które powinny być oczywiste - niezarządzany zasób może być zależny od innych niezarządzanych zasobów, a jeśli zostaną one zutylizowane (lub sfinalizowane) w niewłaściwej kolejności, mogą wystąpić Złe rzeczy.

Jeśli więc instancja klasy w używanym bloku korzysta z niezarządzanych zasobów, to odpowiedź brzmi tak - jest właściwa.

Zauważ jednak, że IDisposable nie nakazuje, aby służył on jedynie do uwalniania niezarządzanych zasobów - tylko że jest to jego główny cel. To może być tak, że autor tej klasy ma pewne działanie, które chcą wymusić dzieje się w określonym czasie, a także wdrażaniu IDisposable może być sposobem na uzyskanie tak się stało, ale czy nie jest to eleganckie rozwiązanie jest coś, co może odpowiedź udzielana jest indywidualnie dla każdego przypadku.

Niezależnie od tego, użycie „using” oznacza, że ​​klasa implementuje IDisposable, więc nie narusza to ekspresji kodu; w gruncie rzeczy jasno pokazuje, co się dzieje.

Maximus Minimus
źródło
„Głównym zastosowaniem tego interfejsu jest uwolnienie niezarządzanych zasobów”. Mówię, że dokumentacja Microsoftu jest do kitu. Może to jak oni używają go w FCL, ale programiści używali go do różnych rzeczy przez blisko dekadę teraz - kilka dobrych powodów, dla niektórych nie-tak-dobrych powodów. Ale zacytowanie tej części ich dokumentu wcale nie odpowiada na pytanie OP.
Jesse C. Slicer
1
Zwróć uwagę na mój ostatni akapit - główny cel nie musi być jedynym celem. IDisposable można zaimplementować z dowolnego powodu i zapewnia on znany interfejs z oczekiwanym zachowaniem i stylem użytkowania, więc kiedy go zobaczysz, wiesz, że jest coś innego, co musi się wydarzyć, zanim instancja będzie mogła wyjść poza zakres. Chodzi o to: jeśli implementuje IDisposable, wówczas użycie jest odpowiednie; wszystko inne jest tylko preambułą.
Maximus Minimus
1
Mówisz w zasadzie, że celem nie jest unikanie czekania na GC, unikanie czekania na finalizator. Ale nie widzę różnicy: finalizator jest wywoływany przez GC.
svick
GC działa w sposób niedeterministyczny - nie wiadomo, kiedy zostanie wywołany. GC również wywołuje finalizator, ale sam się nie pozbywa - sam faktyczny zasób jest poza kontrolą GC. Przy użyciu (1) obiekt jest odpowiednio skalowany, a (2) usuwanie jest znane w znanym momencie - kiedy wychodzi z zakresu bloku używającego, niezarządzany zasób jest usuwany (lub - w najgorszym przypadku - przekazywany coś innego, co je zniszczy). Naprawdę, to tylko drapanie; przeczytaj dokumentację, to wszystko, czego nie powinno się powtarzać.
Maximus Minimus
8

Zastosowanie usingimplikuje obecność Dispose()metody. Inni programiści przyjmą, że taka metoda istnieje na obiekcie. W związku z tym, jeśli przedmiot nie jest jednorazowy, nie powinieneś go używać using.

Klarowność kodu jest królem. Pomiń usinglub zaimplementuj IDisposablena obiekcie.

Wygląda na to, że MiniProfiler używa usingjako mechanizmu „ogrodzenia” profilowanego kodu. Jest w tym trochę zasług; przypuszczalnie MiniProfiler albo wywołuje Dispose()stoper, albo stoper jest zatrzymywany, gdy obiekt MiniProfiler wykracza poza zakres.

Mówiąc bardziej ogólnie, należy wywoływać, usinggdy pewnego rodzaju finalizacja musi nastąpić automatycznie. Dokumentacja html.BeginFormstwierdza, że ​​gdy metoda jest używana w usinginstrukcji, renderuje </form>znacznik zamykający na końcu usingbloku.

To niekoniecznie oznacza, że ​​nadal nie jest to nadużycie.

Robert Harvey
źródło
4
Jak dotąd tak oczywiste. Pytanie brzmi, kiedy (jeśli w ogóle) jest właściwe wdrożenie IDisposable/ Dispose()pomimo braku możliwości dysponowania w sensie opisanym przez OP?
2
Myślałem, że to wyjaśniłem; nie ma czasu, kiedy jest to właściwe.
Robert Harvey
1
Chodzi mi o to, że Disposejest to konieczne, usingaby w ogóle mieć sens (niezależnie od tego, czy jest to właściwe). Wszystkie przykłady OP są implementowane Dispose, prawda? I IIUC, pytanie brzmi, czy to prawda, że ​​te klasy używają Dispose, a nie jakiejś innej metody (jak w Stop()przypadku MiniProfiler).
1
Tak, ale to nie jest pytanie. Pytanie brzmi, czy to dobry pomysł.
2
@delnan: Znów myślałem, że to wyjaśniłem. Jeśli sprawia, że ​​intencje twojego kodu stają się jaśniejsze, to jest to dobry pomysł. Jeśli nie, to nie jest.
Robert Harvey
1

usingjest bezpieczny w przypadku błędów i wyjątków. Zapewnia Dispose()to, że zostanie nazwany niezależnie od błędów popełnianych przez programistę. Nie przeszkadza to w wychwytywaniu lub obsłudze wyjątków, ale Dispose()metoda jest wykonywana rekurencyjnie w górę stosu, gdy zostanie zgłoszony wyjątek.

Obiekty, które implementują, IDisposeale nie mają nic do pozbycia się po ich zakończeniu. Są w najlepszym razie, sprawdzają w przyszłości swój projekt. Abyś nie musiał refaktoryzować kodu źródłowego, kiedy w przyszłości będą musieli się czegoś pozbyć.

Reactgular
źródło