Czy używanie IDisposable i „używanie” jako sposobu uzyskania „określonego zachowania” w celu zapewnienia bezpieczeństwa wyjątków jest nadużyciem?

112

Coś, czego często używałem w C ++, polegało na tym, że klasa Aobsługiwała stan wejścia i wyjścia dla innej klasy B, za pośrednictwem Akonstruktora i destruktora, aby upewnić się, że jeśli coś w tym zakresie wyrzuci wyjątek, to B będzie miał znany stan, gdy zakres został zamknięty. Nie jest to czysty RAII, jeśli chodzi o akronim, ale jest to ustalony wzór.

W C # często chcę to zrobić

class FrobbleManager
{
    ...

    private void FiddleTheFrobble()
    {
        this.Frobble.Unlock();
        Foo();                  // Can throw
        this.Frobble.Fiddle();  // Can throw
        Bar();                  // Can throw
        this.Frobble.Lock();
    }
}

Co należy zrobić w ten sposób

private void FiddleTheFrobble()
{
    this.Frobble.Unlock();

    try
    {            
        Foo();                  // Can throw
        this.Frobble.Fiddle();  // Can throw
        Bar();                  // Can throw
    }
    finally
    {
        this.Frobble.Lock();
    }
}

jeśli chcę zagwarantować Frobblestan w momencie FiddleTheFrobblepowrotu. Kod byłby ładniejszy z

private void FiddleTheFrobble()
{
    using (var janitor = new FrobbleJanitor(this.Frobble))
    {            
        Foo();                  // Can throw
        this.Frobble.Fiddle();  // Can throw
        Bar();                  // Can throw
    }
}

gdzie FrobbleJanitorwygląda mniej więcej jak

class FrobbleJanitor : IDisposable
{
    private Frobble frobble;

    public FrobbleJanitor(Frobble frobble)
    {
        this.frobble = frobble;
        this.frobble.Unlock();
    }

    public void Dispose()
    {
        this.frobble.Lock();
    }
}

I tak właśnie chcę to zrobić. Teraz rzeczywistość dogania, ponieważ to, co ja chcę do użytku wymaga , że FrobbleJanitorstosowany jest z using . Mógłbym uznać to za problem z przeglądaniem kodu, ale coś mnie dręczy.

Pytanie: Czy powyższe zostanie uznane za nadużycie usingi IDisposable?

Johann Gerell
źródło
23
+1 dla samych nazw klas i metod :-). Cóż, żeby być uczciwym, i właściwe pytanie.
Joey
2
@Johannes: Tak, rozumiem, dlaczego - większość ludzi po prostu kręci się na skrzypcach, ale muszę temu zaprotestować. ;-) A tak przy okazji, +1 za podobieństwo do twojego imienia.
Johann Gerell
Być może możesz użyć zakresu lock (this) {..}, aby osiągnąć ten sam wynik w tym przykładzie?
oɔɯǝɹ
1
@ oɔɯǝɹ: Używasz lock (), aby zapobiec jednoczesnemu dostępowi do czegoś z różnych wątków. Nie ma to nic wspólnego ze scenariuszem, który opisuję.
Johann Gerell
1
IScopeable bez Finalizer w następnej wersji C # :)
Jon Raynor

Odpowiedzi:

33

Nie wydaje mi się to koniecznie. IDisposable technicznie jest przeznaczone do pracy na rzeczy, które mają nieodnawialnych udało, ale potem za pomocą dyrektywy jest tylko zgrabny sposób wdrożenia wspólnego wzorca try .. finally { dispose }.

Purysta twierdziłby, że „tak - to nadużycie”, iw purystycznym sensie tak jest; ale większość z nas nie koduje z purystycznej perspektywy, ale z półartystycznej. Moim zdaniem użycie w ten sposób konstrukcji „używając” jest rzeczywiście dość artystyczne.

Prawdopodobnie powinieneś przykleić inny interfejs do IDisposable, aby odsunąć go nieco dalej, wyjaśniając innym programistom, dlaczego ten interfejs implikuje IDisposable.

Jest wiele innych możliwości zrobienia tego, ale ostatecznie nie przychodzi mi do głowy żadna, która byłaby tak zgrabna, jak ta, więc zrób to!

Andras Zoltan
źródło
2
Uważam, że jest to również artystyczne zastosowanie „używania”. Myślę, że post Samuala Jacka odsyłający do komentarza Erica Gunnersona potwierdza tę implementację „używania”. Widzę w tej sprawie doskonałe uwagi zarówno Andrasa, jak i Erica.
IAbstract
1
Rozmawiałem o tym z Ericem Gunnersonem jakiś czas temu i według niego intencją zespołu projektowego C # było, aby użycie oznaczało zakresy. W rzeczywistości zasugerował, że gdyby zaprojektowali użycie przed instrukcją blokującą, może nawet nie istnieć instrukcja blokująca. Byłoby to użycie bloku z wywołaniem monitora lub coś w tym rodzaju. AKTUALIZACJA: Właśnie zdałem sobie sprawę, że następną odpowiedzią jest Eric Lippert, który również jest w zespole językowym. ;) Być może sam zespół C # nie do końca się z tym zgadza? Kontekstem mojej dyskusji z Gunnersonem była klasa TimedLock
Haacked
2
@Haacked - Zabawne, czyż nie; twój komentarz całkowicie potwierdza mój punkt widzenia; że rozmawiałeś z jednym facetem z zespołu i był za tym wszystkim; następnie wskazując poniżej Eric Lippert, z tego samego zespołu nie zgadza się. Z mojego punktu widzenia; C # udostępnia słowo kluczowe, które wykorzystuje interfejs, a także dostarcza ładnie wyglądający wzorzec kodu, który działa w tak wielu scenariuszach. Jeśli nie powinniśmy tego nadużywać, C # powinien znaleźć inny sposób, aby to wymusić. Tak czy inaczej, projektanci prawdopodobnie będą musieli ustawić tutaj swoje kaczki z rzędu! W międzyczasie: using(Html.BeginForm())proszę pana? nie przejmuj się, sir! :)
Andras Zoltan
71

Uważam to za nadużycie instrukcji using. Wiem, że na tym stanowisku jestem w mniejszości.

Uważam to za nadużycie z trzech powodów.

Po pierwsze, ponieważ spodziewam się, że „używanie” jest używane do korzystania z zasobu i pozbycia się go, gdy skończysz . Zmiana stanu programu nie wykorzystuje zasobu, a zmiana go z powrotem nie powoduje usunięcia niczego. Dlatego „używanie” do mutowania i przywracania stanu jest nadużyciem; kod wprowadza w błąd zwykłego czytelnika.

Po drugie, ponieważ oczekuję, że „używanie” będzie używane z grzeczności, a nie z konieczności . Powodem, dla którego używasz "using" do pozbycia się pliku, gdy skończysz z nim, nie jest to, że jest to konieczne , ale dlatego, że jest to grzeczne - ktoś inny może czekać na użycie tego pliku, więc mówi: „gotowe teraz ”jest moralnie poprawną rzeczą do zrobienia. Spodziewam się, że powinienem być w stanie refaktoryzować „użycie”, tak aby zużyty zasób był dłużej zatrzymywany i usuwany później, a jedynym skutkiem takiego działania jest nieznaczne utrudnienie innych procesów, . Blok „używający”, który ma semantyczny wpływ na stan programu ponieważ ukrywa on ważny , wymagała mutacji stanu programu w konstrukcji, która wygląda tak, jakby istniała dla wygody i grzeczności, a nie z konieczności.

Po trzecie, działania twojego programu są określane przez jego stan; właśnie potrzeba starannego manipulowania stanem jest właśnie powodem, dla którego prowadzimy tę rozmowę. Zastanówmy się, jak możemy przeanalizować Twój oryginalny program.

Gdybyś miał to przynieść do przeglądu kodu w moim biurze, pierwsze pytanie, które zadałbym, brzmiało: „czy naprawdę poprawne jest blokowanie frobble, jeśli zostanie zgłoszony wyjątek?” Z twojego programu jasno wynika, że ​​ta rzecz agresywnie ponownie blokuje frobble bez względu na to, co się stanie. Czy to prawda? Zgłoszono wyjątek. Program jest w nieznanym stanie. Nie wiemy, czy Foo, Fiddle czy Bar rzucali, dlaczego rzucali, ani jakie mutacje przeprowadzali w innym stanie, które nie zostało oczyszczone. Czy możesz mnie przekonać , że w tej okropnej sytuacji zawsze dobrze jest ponownie zamknąć zamek?

Może tak jest, może nie jest. Chodzi mi o to, że mając kod w takiej postaci, w jakiej został pierwotnie napisany, recenzent kodu wie, aby zadać pytanie . W kodzie używającym słowa „using” nie umiem zadawać pytania; Zakładam, że blok "using" przydziela zasób, używa go trochę i grzecznie pozbywa się go po zakończeniu , a nie, że nawias zamykający bloku "using" mutuje mój stan programu w wyjątkowych okolicznościach, gdy jest dowolnie wiele warunki zgodności stanu programu zostały naruszone.

Użycie bloku „using” w celu uzyskania efektu semantycznego powoduje, że program jest fragmentem:

}

niezwykle znaczące. Kiedy patrzę na tę pojedynczą klamrę zamykającą, nie myślę od razu, że „ta klamra ma skutki uboczne, które mają daleko idący wpływ na globalny stan mojego programu”. Ale kiedy nadużywasz „używania” w ten sposób, nagle to robi.

Drugą rzeczą, którą chciałbym zapytać, gdybym zobaczył twój oryginalny kod, jest „co się stanie, jeśli wyjątek zostanie zgłoszony po odblokowaniu, ale przed wprowadzeniem próby?” Jeśli używasz niezoptymalizowanego zestawu, kompilator mógł wstawić instrukcję no-op przed próbą i jest możliwe, że wyjątek przerwania wątku wystąpi w przypadku braku operacji. Jest to rzadkie, ale zdarza się w prawdziwym życiu, szczególnie na serwerach internetowych. W takim przypadku następuje odblokowanie, ale blokada nigdy się nie dzieje, ponieważ wyjątek został zgłoszony przed próbą. Jest całkowicie możliwe, że ten kod jest podatny na ten problem i powinien zostać napisany

bool needsLock = false;
try
{
    // must be carefully written so that needsLock is set
    // if and only if the unlock happened:

    this.Frobble.AtomicUnlock(ref needsLock);
    blah blah blah
}
finally
{
    if (needsLock) this.Frobble.Lock();
}

Znowu, może tak, może nie, ale wiem mogę zadać pytanie . W przypadku wersji używającej jest podatny na ten sam problem: wyjątek przerwania wątku może zostać zgłoszony po zablokowaniu Frobble, ale przed wprowadzeniem regionu chronionego przed próbą skojarzonego z użyciem. Ale z wersją „używającą” zakładam, że jest to „i co z tego?” sytuacja. Szkoda, że ​​tak się stanie, ale zakładam, że „używanie” jest tylko po to, aby być uprzejmym, a nie po to, aby mutować niezwykle ważny stan programu. Zakładam, że jeśli jakiś straszny wyjątek przerwania wątku zdarzy się dokładnie w niewłaściwym czasie, to cóż, moduł odśmiecania pamięci wyczyści ten zasób, uruchamiając finalizator.

Eric Lippert
źródło
18
Chociaż odpowiedziałbym na to pytanie inaczej, myślę, że to dobrze uzasadniony post. Jednak nie zgadzam się z pańskim twierdzeniem, które usingjest raczej kwestią uprzejmości niż poprawności. Uważam, że wycieki zasobów to problemy z poprawnością, chociaż często są na tyle niewielkie, że nie są błędami o wysokim priorytecie. Zatem usingbloki już mają semantyczny wpływ na stan programu, a tym, co zapobiegają, jest nie tylko „niedogodność” dla innych procesów, ale prawdopodobnie zepsute środowisko. Nie oznacza to, że inny rodzaj wpływu semantycznego wynikający z pytania jest również odpowiedni.
kvb
13
Wycieki zasobów to problemy z poprawnością, tak. Jednak niepowodzenie w użyciu „używania” nie powoduje wycieku zasobu; zasób zostanie ostatecznie wyczyszczony po uruchomieniu finalizatora. Porządkowanie może nie być tak agresywne i na czas, jak byś chciał, ale tak się stanie.
Eric Lippert
7
Cudowny post, oto moje dwa grosze :) Podejrzewam, że wiele "nadużyć" usingwynika z tego, że jest to tkacz zorientowany na aspekt biednego człowieka w C #. To, czego naprawdę chce programista, to sposób na zagwarantowanie, że jakiś wspólny kod będzie działał na końcu bloku bez konieczności jego duplikowania. C # nie obsługuje dzisiaj AOP (nie dotyczy MarshalByRefObject i PostSharp). Jednak transformacja instrukcji using przez kompilator umożliwia osiągnięcie prostego AOP poprzez zmianę semantyki deterministycznego usuwania zasobów. Chociaż nie jest to dobra praktyka, czasami warto
odpuścić
11
Chociaż generalnie zgadzam się z twoim stanowiskiem, są pewne przypadki, w których korzyść z zmiany przeznaczenia usingjest zbyt atrakcyjna, by się poddać. Najlepszym przykładem, jaki przychodzi mi do głowy, jest śledzenie i raportowanie czasów kodu: using( TimingTrace.Start("MyMethod") ) { /* code */ } znowu jest to AOP - Start()rejestruje czas rozpoczęcia przed rozpoczęciem bloku, Dispose()przechwytuje czas zakończenia i rejestruje aktywność. Nie zmienia stanu programu i nie reaguje na wyjątki. Jest również atrakcyjny, ponieważ unika sporo hydrauliki i jest często używany jako wzorzec łagodzący mylącą semantykę.
LBushkin
6
Zabawne, zawsze myślałem o użyciu jako środka do wspierania RAII. Uważam, że zamek jest rodzajem zasobu dość intuicyjnym.
Brian
27

Jeśli potrzebujesz tylko czystego kodu o określonym zakresie, możesz również użyć lambd, a la

myFribble.SafeExecute(() =>
    {
        myFribble.DangerDanger();
        myFribble.LiveOnTheEdge();
    });

gdzie .SafeExecute(Action fribbleAction)metoda zawija try- catch- finallyblok.

herzmeister
źródło
W tym przykładzie przegapiłeś stan wejścia. Ponadto, upewnij się, owinąć try / finally, ale nie mów nic w końcu, więc jeśli coś w lambda rzuca nie masz pojęcia, w jakim stanie jesteś w.
Johann Gerell
1
Ach! Ok, myślę, że masz na myśli, że SafeExecutejest to Fribblemetoda, która wywołuje Unlock()i Lock()zapakowaną try / w końcu. W takim razie przepraszam. Od razu uznałem, że SafeExecutejest to ogólna metoda rozszerzenia i dlatego wspomniałem o braku stanu wejścia i wyjścia. Mój błąd.
Johann Gerell
1
Bądź bardzo ostrożny z tym apporach. W przypadku schwytanych miejscowych mogą wystąpić niebezpieczne, subtelne, niezamierzone przedłużenia życia!
jason
4
Fuj. Po co ukrywać try / catch / w końcu? Sprawia, że ​​Twój kod jest ukrywany, aby mógł go przeczytać innym
Frank Schwieterman
2
@Yukky Frank: „Ukrywanie” czegokolwiek nie było moim pomysłem, była to prośba pytającego. :-P ... To powiedziawszy, prośba jest w końcu kwestią „Nie powtarzaj się”. Możesz mieć wiele metod, które wymagają tej samej standardowej „ramki” czystego pobierania / zwalniania czegoś i nie chcesz narzucać tego wywołującemu (pomyśl o hermetyzacji). Można również wyraźniej nazwać metody takie jak .SafeExecute (...), aby wystarczająco dobrze komunikować się z tym, co robią.
herzmeister
25

Eric Gunnerson, który był członkiem zespołu projektantów języka C #, udzielił tej odpowiedzi na prawie to samo pytanie:

Doug pyta:

re: Instrukcja blokady z limitem czasu ...

Zrobiłem tę sztuczkę wcześniej, aby poradzić sobie z typowymi wzorcami w wielu metodach. Zwykle pozyskiwanie zamków , ale jest kilka innych . Problem polega na tym, że zawsze wydaje się, że jest to włamanie, ponieważ obiekt nie jest tak naprawdę jednorazowy, a raczej „ oddzwaniający-na-koniec-zakresu ”.

Doug,

Kiedy zdecydowaliśmy [sic] na instrukcję using, zdecydowaliśmy się nazwać ją „using”, a nie czymś bardziej konkretnym do usuwania obiektów, tak aby można ją było wykorzystać dokładnie w tym scenariuszu.

Samuel Jack
źródło
4
Cóż, ten cytat powinien powiedzieć, o czym miał na myśli, mówiąc „dokładnie taki scenariusz”, ponieważ wątpię, czy odnosił się do tego przyszłego pytania.
Frank Schwieterman
4
@Frank Schwieterman: Uzupełniłem wycenę. Najwyraźniej osoby z zespołu C # uważały, że usingfunkcja nie ogranicza się do usuwania zasobów.
paercebal
11

To jest śliskie zbocze. IDisposable ma kontrakt, który jest zabezpieczony przez finalizatora. Finalizator jest w Twoim przypadku bezużyteczny. Nie możesz zmusić klienta do używania instrukcji using, a jedynie zachęcić go do tego. Możesz to wymusić za pomocą takiej metody:

void UseMeUnlocked(Action callback) {
  Unlock();
  try {
    callback();
  }
  finally {
    Lock();
  }
}

Ale bez lamd wydaje się to trochę niezręczne. To powiedziawszy, użyłem IDisposable, tak jak ty.

W twoim poście jest jednak szczegół, który sprawia, że ​​jest to niebezpiecznie bliskie anty-wzorowi. Wspomniałeś, że te metody mogą zgłosić wyjątek. Nie jest to coś, co dzwoniący może zignorować. Może z tym zrobić trzy rzeczy:

  • Nic nie rób, wyjątku nie można odzyskać. Normalny przypadek. Wywołanie Unlocka nie ma znaczenia.
  • Złap i obsłuż wyjątek
  • Przywróć stan w jego kodzie i pozwól wyjątkowi przejść przez łańcuch wywołań.

Te dwa ostatnie wymagają, aby wywołujący jawnie napisał blok try. Teraz na przeszkodzie staje instrukcja using. Może to spowodować zapadnięcie klienta w śpiączkę, która sprawi, że uwierzy, że twoja klasa dba o stan i nie trzeba wykonywać żadnej dodatkowej pracy. To prawie nigdy nie jest dokładne.

Hans Passant
źródło
Wydaje mi się, że wymuszony przypadek został przedstawiony przez „herzmeister der welten” powyżej - nawet jeśli PO nie podobał się ten przykład.
Benjamin Podszun
Tak, zinterpretowałem jego SafeExecutejako ogólną metodę rozszerzenia. Teraz widzę, że prawdopodobnie chodziło o Fribblemetodę, która wywołuje Unlock()i Lock()w zapakowanym try / w końcu. Mój błąd.
Johann Gerell
Zignorowanie wyjątku nie oznacza, że ​​nie można go odzyskać. Oznacza to, że będzie obsługiwany powyżej w stosie. Na przykład wyjątki mogą służyć do prawidłowego wychodzenia z wątku. W takim przypadku musisz poprawnie uwolnić swoje zasoby, w tym zablokowane zamki.
paercebal
7

Przykładem ze świata rzeczywistego jest BeginForm z ASP.net MVC. Zasadniczo możesz napisać:

Html.BeginForm(...);
Html.TextBox(...);
Html.EndForm();

lub

using(Html.BeginForm(...)){
    Html.TextBox(...);
}

Html.EndForm wywołuje Dispose, a Dispose po prostu wyprowadza </form> tag. Zaletą tego jest to, że nawiasy {} tworzą widoczny „zakres”, który ułatwia zobaczenie, co jest w formularzu, a co nie.

Nie nadużywałbym tego, ale zasadniczo IDisposable to po prostu sposób na powiedzenie „MUSISZ wywołać tę funkcję, kiedy skończysz”. MvcForm używa go, aby upewnić się, że formularz jest zamknięty, Stream używa go, aby upewnić się, że strumień jest zamknięty, możesz go użyć, aby upewnić się, że obiekt jest odblokowany.

Osobiście użyłbym go tylko wtedy, gdy poniższe dwie reguły są prawdziwe, ale są one ustalone przeze mnie arbitralnie:

  • Dispose powinna być funkcją, która zawsze musi działać, więc nie powinno być żadnych warunków poza sprawdzeniami zerowymi
  • Po operacji Dispose () obiekt nie powinien nadawać się do ponownego użycia. Gdybym chciał obiektu wielokrotnego użytku, wolałbym raczej podać mu metody otwierania / zamykania niż wyrzucać. Więc rzucam InvalidOperationException podczas próby użycia usuniętego obiektu.

W końcu chodzi o oczekiwania. Jeśli obiekt implementuje IDisposable, zakładam, że musi wykonać pewne porządki, więc nazywam to. Myślę, że zwykle bije to z funkcją „Shutdown”.

Biorąc to pod uwagę, nie podoba mi się ta linia:

this.Frobble.Fiddle();

Ponieważ FrobbleJanitor „posiada” teraz Frobble'a, zastanawiam się, czy nie byłoby lepiej wezwać Fiddle'a na jego Frobble in the Janitor?

Michael Stum
źródło
O twoim „Nie podoba mi się ta linia” - właściwie przez chwilę myślałem o zrobieniu tego w ten sposób, formułując pytanie, ale nie chciałem zaśmiecać semantyki mojego pytania. Ale trochę się z tobą zgadzam. Rodzaj. :)
Johann Gerell
1
Głosowano za dostarczeniem przykładu od samego Microsoft. Zwróć uwagę, że w przypadku, gdy wspomnisz, istnieje określony wyjątek: ObjectDisposedException.
Antitoon
4

W naszej bazie kodu używamy tego wzorca w wielu miejscach i widziałem go już wcześniej - jestem pewien, że musiał być również tutaj omówiony. Ogólnie nie widzę, co jest w tym złego, zapewnia to użyteczny wzorzec i nie powoduje rzeczywistej szkody.

Simon Steele
źródło
4

Wbijając w to: zgadzam się z większością tutaj, że jest to kruche, ale przydatne. Chciałbym skierować Cię do klasy System.Transaction.TransactionScope , która robi coś takiego, jak chcesz.

Generalnie podoba mi się składnia, która usuwa wiele bałaganu z prawdziwego mięsa. Proszę jednak rozważyć nadanie klasie pomocniczej dobrej nazwy - może ... Zakres, jak na powyższym przykładzie. Nazwa powinna zdradzać, że zawiera fragment kodu. * Zakres, * Blok lub coś podobnego powinno to zrobić.

Benjamin Podszun
źródło
1
„Janitor” pochodzi ze starego artykułu w C ++ autorstwa Andrei Alexandrescu o osłonach lunety, jeszcze zanim ScopeGuard dotarł do Lokiego.
Johann Gerell
@Benjamin: Podoba mi się klasa TransactionScope, wcześniej o niej nie słyszałem. Może dlatego, że jestem w krainie .Net CF, gdzie nie jest to uwzględnione ... :-(
Johann Gerell
4

Uwaga: mój punkt widzenia jest prawdopodobnie odchylony od mojego tła w C ++, więc wartość mojej odpowiedzi powinna być oceniana pod kątem tego możliwego błędu ...

Co mówi specyfikacja języka C #?

Cytowanie specyfikacji języka C # :

8.13 Instrukcja using

[…]

Zasób jest klasa lub struct, który implementuje System.IDisposable, który zawiera jedną metodę bez parametrów nazwie utylizować. Kod korzystający z zasobu może wywołać metodę Dispose, aby wskazać, że zasób nie jest już potrzebny. Jeśli funkcja Dispose nie zostanie wywołana, w końcu nastąpi automatyczne usuwanie w wyniku wyrzucania elementów bezużytecznych.

Kod, który korzysta z zasobu jest, oczywiście, kod zaczynający przez usingsłowa kluczowego i dzieje się aż do zakresu dołączonym do using.

Więc myślę, że to jest w porządku, ponieważ blokada jest zasobem.

Być może słowo kluczowe usingzostało źle dobrane. Może powinien był zostać nazwany scoped.

Wtedy możemy uznać prawie wszystko za zasób. Uchwyt pliku. Połączenie sieciowe ... Wątek?

Wątek???

Używanie (lub nadużywanie) usingsłowa kluczowego?

Czy byłoby błyszczące użycie (ab) usingsłowa kluczowego, aby upewnić się, że praca wątku została zakończona przed wyjściem z zakresu?

Herb Sutter wydaje się uważać, że jest błyszczący , ponieważ oferuje interesujące wykorzystanie wzoru IDispose do czekania na zakończenie pracy nici:

http://www.drdobbs.com/go-parallel/article/showArticle.jhtml?articleID=225700095

Oto kod wklejony z artykułu:

// C# example
using( Active a = new Active() ) {    // creates private thread
       
       a.SomeWork();                  // enqueues work
       
       a.MoreWork();                   // enqueues work
       
} // waits for work to complete and joins with private thread

Chociaż kod C # dla obiektu Active nie jest dostarczany, jest napisany, że C # używa wzorca IDispose dla kodu, którego wersja C ++ jest zawarta w destruktorze. Patrząc na wersję C ++, widzimy destruktor, który czeka na zakończenie wewnętrznego wątku przed wyjściem, jak pokazano w tym drugim fragmencie artykułu:

~Active() {
    // etc.
    thd->join();
 }

Więc jeśli chodzi o Herb, to jest błyszczący .

paercebal
źródło
3

Uważam, że odpowiedź na twoje pytanie brzmi: nie, nie byłoby to nadużycie IDisposable.

Sposób, w jaki rozumiem IDisposableinterfejs, jest taki, że nie powinieneś pracować z obiektem, gdy zostanie on usunięty (z wyjątkiem tego, że możesz wywoływać jego Disposemetodę tak często, jak chcesz).

Ponieważ za każdym razem, gdy docierasz do instrukcji, tworzysz nową FrobbleJanitor jawnie using, nigdy nie używasz FrobbeJanitordwukrotnie tego samego obiektu. A ponieważ celem jest zarządzanie innym obiektem,Dispose wydaje się odpowiedni do zadania zwalniania tego („zarządzanego”) zasobu.

(Przy okazji, standardowy przykładowy kod demonstrujący prawidłową implementację Disposeprawie zawsze sugeruje, że zasoby zarządzane powinny być również zwolnione, a nie tylko niezarządzane zasoby, takie jak uchwyty systemu plików).

Jedyną rzeczą, o którą osobiście bym się martwił, jest to, że mniej jasne jest, co się dzieje, using (var janitor = new FrobbleJanitor())niż z bardziej wyraźnym try..finallyblokiem, w którym operacje Lock& Unlocksą bezpośrednio widoczne. Ale przyjęte podejście sprowadza się prawdopodobnie do osobistych preferencji.

stakx - już nie wnoszący wkładu
źródło
1

To nie jest obraźliwe. Używasz ich do tego, do czego są stworzone. Ale być może będziesz musiał rozważyć siebie w zależności od potrzeb. Na przykład, jeśli zdecydujesz się na „artyzm”, możesz użyć „używając”, ale jeśli twój fragment kodu jest wykonywany dużo razy, to ze względów wydajnościowych możesz użyć konstrukcji „try” .. „w końcu”. Ponieważ „używanie” zwykle wiąże się z tworzeniem przedmiotu.

ata
źródło
1

Myślę, że zrobiłeś to dobrze. Przeciążenie metody Dispose () byłoby problemem, który ta sama klasa miała później wyczyścić, co faktycznie musiała zrobić, a czas życia tego czyszczenia zmienił się i był inny niż czas, w którym spodziewano się przytrzymania blokady. Ale ponieważ stworzyłeś oddzielną klasę (FrobbleJanitor), która jest odpowiedzialna tylko za blokowanie i odblokowywanie Frobble, rzeczy są na tyle rozdzielone, że nie trafisz w ten problem.

Zmieniłbym jednak nazwę FrobbleJanitor, prawdopodobnie na coś takiego jak FrobbleLockSession.

Frank Schwieterman
źródło
O zmianie nazwy - użyłem „Janitor” z głównego powodu, dla którego miałem do czynienia z blokowaniem / odblokowywaniem. Myślałem, że to rymuje się z innymi wybranymi nazwami. ;-) Gdybym użył tej metody jako wzorca w całym moim kodzie, z pewnością zawarłbym coś bardziej ogólnie opisowego, jak zasugerowałeś w przypadku „Sesji”. Gdyby to było stare dni C ++, prawdopodobnie użyłbym FrobbleUnlockScope.
Johann Gerell