try / catch + using, właściwa składnia

189

Który:

using (var myObject = new MyClass())
{
   try
   {
      // something here...
   }
   catch(Exception ex)
   {
      // Handle exception
   }
}

LUB

try
{
   using (var myObject = new MyClass())
   {
      // something here...
   }
}
catch(Exception ex)
{
   // Handle exception
}
Xaqron
źródło
7
Tylko uwaga: należy zachować ostrożność, aby wychwytywać tylko wyjątki, które faktycznie można obsłużyć (skorygować), z wyjątkiem logowania lub zawijania.
John Saunders
1
Proszę pamiętać, że to ostatni }z usingoświadczeniem może rzucić wyjątek jako przypomnienia tutaj .
Giulio Caccin,
1
TIL, że debugger (w VS) nie wywoła metody dispose, jeśli użyjesz pierwszego bloku kodu. Ponieważ sama instrukcja using może zgłosić wyjątek, pomaga mi użyć drugiego bloku, aby zapewnić finallydomyślną metodę zwaną dispose.
ShooShoSha

Odpowiedzi:

98

Wolę drugi. Może również uwięzić błędy związane z tworzeniem obiektu.

Jonathan Wood
źródło
11
Nie zgadzam się z tą radą. Jeśli spodziewasz się, że tworzenie obiektu wygeneruje błąd, wówczas każda obsługa tego wyjątku musi wyjść na zewnątrz. Jeśli jest jakieś pytanie dotyczące tego, gdzie powinna się odbywać obsługa, wówczas oczekiwanym wyjątkiem musi być coś innego - chyba że opowiadasz się za złapaniem dowolnego przypadkowego wyjątku, który może, ale nie musi być przewidziany, co jest klasycznym anty-wzorem (poza proces lub nieobsługiwany moduł obsługi wyjątków wątku).
Jeffrey L Whitledge
1
@Jeffrey: Opisane przeze mnie podejście dobrze mi służyło i robię to od dłuższego czasu. Nikt nie powiedział nic o spodziewaniu się niepowodzenia tworzenia obiektu. Ale zawijając operację, która może potencjalnie nie powieść w trybloku, co pozwala na wyświetlenie komunikatu o błędzie, jeśli coś się nie powiedzie, program ma teraz możliwość odzyskania i poinformowania użytkownika.
Jonathan Wood
Twoja odpowiedź jest poprawna, ale nadal sugeruje, że próbowanie / złapanie musi być zawsze dostępne (natychmiast).
Henk Holterman
17
Myślę, że pierwszy ma również zalety, rozważ transakcję DB, using( DBConnection conn = DBFactory.getConnection())która musiałaby zostać wycofana w przypadku wystąpienia wyjątku. Wydaje mi się, że oboje mają swoje miejsce.
wfoster
1
Spowoduje to również pułapkę błędów związanych ze zbyciem obiektu.
Ahmad Ibrahim
39

Ponieważ blok używający jest po prostu uproszczeniem składni try / wreszcie ( MSDN ), osobiście wybrałbym następujące, choć wątpię, aby znacznie różniło się od twojej drugiej opcji:

MyClass myObject = null;
try {
  myObject = new MyClass();
  //important stuff
} catch (Exception ex) {
  //handle exception
} finally {
  if(myObject is IDisposable) myObject.Dispose();
}
chezy525
źródło
4
Jak myślisz, dlaczego dodanie finallybloku jest lepsze od usinginstrukcji?
Cody Gray
10
Dodanie finallybloku, który usuwa obiekt IDisposable, jest tym, co usingrobi instrukcja. Osobiście podoba mi się to zamiast osadzonego usingbloku, ponieważ uważam, że bardziej czysto stwierdza, gdzie wszystko się dzieje i że wszystko jest na tym samym „poziomie”. Podoba mi się również więcej niż kilka osadzonych usingbloków ... ale to wszystko po prostu moje preferencje.
chezy525
8
Jeśli wdrożysz wiele procedur obsługi wyjątków, musisz naprawdę cieszyć się pisaniem! To słowo „używanie” istnieje już od jakiegoś czasu i jego znaczenie jest dla mnie całkiem jasne. A użycie tego pomaga wyczyścić resztę mojego kodu, ograniczając ilość bałaganu do minimum.
Jonathan Wood
2
To jest niepoprawne. Obiekt musi zostać utworzony poza instancją, tryaby mógł zostać umieszczony w finallyinstrukcji; w przeciwnym razie, będzie rzucać błąd kompilatora: „Wykorzystanie nieprzydzielonej zmiennej lokalnej«myObject»”
Steve Konves
3
Technicznie też się nie skompiluje. Cannot assign null to implicitly-typed local variable;) Ale wiem, co masz na myśli i osobiście wolałbyś to niż zagnieżdżanie bloku używanego.
Connell,
20

To zależy. Jeśli korzystasz z Windows Communication Foundation (WCF), using(...) { try... }nie będzie działać poprawnie, jeśli serwer proxy w usinginstrukcji jest w stanie wyjątku, tzn. Usunięcie tego serwera proxy spowoduje kolejny wyjątek.

Osobiście wierzę w minimalne podejście do obsługi, tj. Obsługuj tylko wyjątki, o których wiesz w momencie wykonania. Innymi słowy, jeśli wiesz, że inicjalizacja zmiennej usingmoże wywołać określony wyjątek, to ją zawijam try-catch. Podobnie, jeśli w usingciele może się zdarzyć coś, co nie jest bezpośrednio związane ze zmienną w using, to zawijam to innym trydla tego wyjątku. Rzadko używam Exceptionw swoimcatch es.

Ale mi się podoba IDisposablei usingmoże dlatego jestem stronniczy.

Schultz9999
źródło
19

Jeśli instrukcja catch musi uzyskać dostęp do zmiennej zadeklarowanej w instrukcji using, wówczas wewnątrz jest twoją jedyną opcją.

Jeśli instrukcja catch potrzebuje obiektu, do którego istnieje odwołanie, zanim zostanie usunięta, wówczas wewnątrz jest twoją jedyną opcją.

Jeśli instrukcja catch podejmie działanie o nieznanym czasie trwania, np. Wyświetli komunikat dla użytkownika, a chcesz zutylizować swoje zasoby, zanim to nastąpi, wtedy najlepszym rozwiązaniem jest wyjście na zewnątrz.

Ilekroć mam scenerię podobną do tej, blok try-catch jest zwykle w innej metodzie w górę stosu wywołań niż za pomocą. Nie jest typowe dla tej metody, aby wiedzieć, jak obsługiwać wyjątki, które występują w niej w ten sposób.

Więc moja ogólna rekomendacja jest na zewnątrz - na zewnątrz.

private void saveButton_Click(object sender, EventArgs args)
{
    try
    {
        SaveFile(myFile); // The using statement will appear somewhere in here.
    }
    catch (IOException ex)
    {
        MessageBox.Show(ex.Message);
    }
}
Jeffrey L. Whitledge
źródło
10

Obie są poprawną składnią. To naprawdę sprowadza się do tego, co chcesz zrobić: jeśli chcesz wyłapać błędy związane z tworzeniem / usuwaniem obiektu, użyj drugiego. Jeśli nie, użyj pierwszego.

Smashery
źródło
8

Przywołam tutaj jedną ważną rzecz: pierwsza nie złapie żadnego wyjątku wynikającego z wywołania MyClasskonstruktora.

Madhur Ahuja
źródło
3

Od C # 8.0 wolę używać tego samego w ten sam sposób

public class Person : IDisposable
{
    public Person()
    {
        int a = 0;
        int b = Id / a;
    }
    public int Id { get; set; }

    public void Dispose()
    {
    }
}

i wtedy

static void Main(string[] args)
    {

        try
        {
            using var person = new Person();
        }
        catch (Exception ex) when
        (ex.TargetSite.DeclaringType.Name == nameof(Person) &&
        ex.TargetSite.MemberType == System.Reflection.MemberTypes.Constructor)
        {
            Debug.Write("Error Constructor Person");
        }
        catch (Exception ex) when
       (ex.TargetSite.DeclaringType.Name == nameof(Person) &&
       ex.TargetSite.MemberType != System.Reflection.MemberTypes.Constructor)
        {
            Debug.Write("Error Person");
        }
        catch (Exception ex)
        {
            Debug.Write(ex.Message);
        }
        finally
        {
            Debug.Write("finally");
        }
    }
Reza Jenabi
źródło
1

Jeśli obiekt, który inicjujesz w bloku Using (), może zgłosić wyjątek, powinieneś przejść do drugiej składni, w przeciwnym razie obie są równie poprawne.

W moim scenariuszu musiałem otworzyć plik i przekazałem filePath do konstruktora obiektu, który inicjowałem w bloku Using (), i może wygenerować wyjątek, jeśli filePath jest niepoprawny / pusty. W tym przypadku druga składnia ma sens.

Mój przykładowy kod: -

try
{
    using (var obj= new MyClass("fileName.extension"))
    {

    }
}
catch(Exception ex)
{
     //Take actions according to the exception.
}
Ankur Arora
źródło
1

Od wersji C # 8.0 możesz uprościćusing instrukcje pod pewnymi warunkami, aby pozbyć się zagnieżdżonego bloku, a następnie dotyczy to tylko otaczającego bloku.

Twoje dwa przykłady można sprowadzić do:

using var myObject = new MyClass();
try
{
   // something here...
}
catch(Exception ex)
{
   // Handle exception
}

I:

try
{
   using var myObject = new MyClass();
   // something here...
}
catch(Exception ex)
{
   // Handle exception
}

Oba są dość jasne; a następnie ogranicza to wybór między nimi do kwestii tego, jaki ma być zakres obiektu, gdzie chcesz obsługiwać błędy instancji i kiedy chcesz go pozbyć.

Jason C.
źródło