Coś, czego często używałem w C ++, polegało na tym, że klasa A
obsługiwała stan wejścia i wyjścia dla innej klasy B
, za pośrednictwem A
konstruktora 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ć Frobble
stan w momencie FiddleTheFrobble
powrotu. 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 FrobbleJanitor
wyglą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 FrobbleJanitor
stosowany 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 using
i IDisposable
?
źródło
Odpowiedzi:
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!
źródło
using(Html.BeginForm())
proszę pana? nie przejmuj się, sir! :)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
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.
źródło
using
jest 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. Zatemusing
bloki 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.using
wynika 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 wartousing
jest 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ę.Jeśli potrzebujesz tylko czystego kodu o określonym zakresie, możesz również użyć lambd, a la
gdzie
.SafeExecute(Action fribbleAction)
metoda zawijatry
-catch
-finally
blok.źródło
SafeExecute
jest toFribble
metoda, która wywołujeUnlock()
iLock()
zapakowaną try / w końcu. W takim razie przepraszam. Od razu uznałem, żeSafeExecute
jest to ogólna metoda rozszerzenia i dlatego wspomniałem o braku stanu wejścia i wyjścia. Mój błąd.Eric Gunnerson, który był członkiem zespołu projektantów języka C #, udzielił tej odpowiedzi na prawie to samo pytanie:
źródło
using
funkcja nie ogranicza się do usuwania zasobów.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:
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:
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.
źródło
SafeExecute
jako ogólną metodę rozszerzenia. Teraz widzę, że prawdopodobnie chodziło oFribble
metodę, która wywołujeUnlock()
iLock()
w zapakowanym try / w końcu. Mój błąd.Przykładem ze świata rzeczywistego jest BeginForm z ASP.net MVC. Zasadniczo możesz napisać:
lub
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:
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:
Ponieważ FrobbleJanitor „posiada” teraz Frobble'a, zastanawiam się, czy nie byłoby lepiej wezwać Fiddle'a na jego Frobble in the Janitor?
źródło
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.
źródło
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ć.
źródło
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 # :
Kod, który korzysta z zasobu jest, oczywiście, kod zaczynający przez
using
słowa kluczowego i dzieje się aż do zakresu dołączonym dousing
.Więc myślę, że to jest w porządku, ponieważ blokada jest zasobem.
Być może słowo kluczowe
using
zostało źle dobrane. Może powinien był zostać nazwanyscoped
.Wtedy możemy uznać prawie wszystko za zasób. Uchwyt pliku. Połączenie sieciowe ... Wątek?
Wątek???
Używanie (lub nadużywanie)
using
słowa kluczowego?Czy byłoby błyszczące użycie (ab)
using
sł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:
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:
Więc jeśli chodzi o Herb, to jest błyszczący .
źródło
Uważam, że odpowiedź na twoje pytanie brzmi: nie, nie byłoby to nadużycie
IDisposable
.Sposób, w jaki rozumiem
IDisposable
interfejs, jest taki, że nie powinieneś pracować z obiektem, gdy zostanie on usunięty (z wyjątkiem tego, że możesz wywoływać jegoDispose
metodę tak często, jak chcesz).Ponieważ za każdym razem, gdy docierasz do instrukcji, tworzysz nową
FrobbleJanitor
jawnieusing
, nigdy nie używaszFrobbeJanitor
dwukrotnie 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ę
Dispose
prawie 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źnymtry..finally
blokiem, w którym operacjeLock
&Unlock
są bezpośrednio widoczne. Ale przyjęte podejście sprowadza się prawdopodobnie do osobistych preferencji.źródło
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.
źródło
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.
źródło