.NET istisnalarını yakalamak ve yeniden atmak için en iyi uygulamalar


284

İstisnaları yakalayıp yeniden atarken dikkate alınması gereken en iyi uygulamalar nelerdir? ExceptionNesnenin InnerExceptionve yığın izlemesinin korunduğundan emin olmak istiyorum . Aşağıdaki kod blokları arasında, bunların işlenme biçiminde bir fark var mı?

try
{
    //some code
}
catch (Exception ex)
{
    throw ex;
}

Vs:

try
{
    //some code
}
catch
{
    throw;
}

Yanıtlar:


262

Yığın izini korumanın yolu, throw;Bu da geçerlidir.

try {
  // something that bombs here
} catch (Exception ex)
{
    throw;
}

throw ex;temelde bu noktadan bir istisna atmak gibidir, bu nedenle yığın izlemesi yalnızca throw ex;ifadeyi yayınladığınız yere gider .

Mike da doğrudur, istisnanın bir istisna geçirmenize izin verdiği varsayılarak (önerilir).

Karl Seguin bir sahiptir taşıma istisna kadar büyük bir yazma onun içinde e-kitap programlama temelleri büyük bir okuma yanı sıra, hangi.

Düzenle: Programlamanın Temelleri çalışma pdf. Metinde "istisna" ifadesini aramanız yeterlidir.


10
Bu yazma harika ise o kadar emin değilim, denemek {// ...} catch (Exception ex) {yeni istisna atmak (ex.Message + "diğer şeyler"); } iyidir. Sorun, tüm istisnaları yakalamadığınız sürece, bu istisnayı yığının yukarısına daha fazla işleyememenizdir, büyük bir hayır-hayır (bu OutOfMemoryException'ı işlemek istediğinizden emin misiniz?)
ljs

2
@ljs Tavsiye ettiğim herhangi bir bölüm görmediğim için yorumunuzdan bu yana makale değişti. Aslında bunun tam tersi, bunu yapmamasını söylüyor ve OutOfMemoryException'ı da ele almak isteyip istemediğinizi soruyor !?
RyanfaeScotland

6
Bazen atın; yığın izini korumak için yeterli değildir. İşte bir örnek https://dotnetfiddle.net/CkMFoX
Artavazd Balayan

4
Veya ExceptionDispatchInfo.Capture(ex).Throw(); throw;.NET +4.5'te stackoverflow.com/questions/57383/…
Alfred Wallace

@ AlfredWallace çözümü benim için mükemmel çalıştı. deneyin {...} catch {throw} yığın izlemesini korumadı. Teşekkür ederim.
atownson

100

İlk istisna ile yeni bir istisna atarsanız, ilk yığın izini de koruyacaksınız.

try{
} 
catch(Exception ex){
     throw new MoreDescriptiveException("here is what was happening", ex);
}

Ne denesem denesem yeni Exception ("message", ex) her zaman ex atar ve özel mesajı yok sayar. yeni istisna atmak ("mesaj", ex.InnerException) olsa çalışır.
Tod

Özel bir istisna gerekmiyorsa, AggregateException (.NET 4+) kullanılabilir msdn.microsoft.com/en-us/library/…
Nikos Tsokos

AggregateExceptionyalnızca toplu işlemler üzerindeki istisnalar için kullanılmalıdır. Örneğin, CLR'nin ParallelEnumerableve Tasksınıfları tarafından atılır . Kullanım muhtemelen bu örneği takip etmelidir.
Aluan Haddad

29

Aslında, throwifadenin StackTrace bilgilerini korumayacağı bazı durumlar vardır . Örneğin, aşağıdaki kodda:

try
{
  int i = 0;
  int j = 12 / i; // Line 47
  int k = j + 1;
}
catch
{
  // do something
  // ...
  throw; // Line 54
}

StackTrace, satır 47'de yükseltilmesine rağmen, satır 54'ün istisnayı yükselttiğini gösterecektir.

Unhandled Exception: System.DivideByZeroException: Attempted to divide by zero.
   at Program.WithThrowIncomplete() in Program.cs:line 54
   at Program.Main(String[] args) in Program.cs:line 106

Yukarıda açıklananlara benzer durumlarda, orijinal StackTrace'i sunmak için iki seçenek vardır:

İstisna Çağırma.InternalPreserveStackTrace

Özel bir yöntem olduğundan, yansıma kullanılarak çağrılmalıdır:

private static void PreserveStackTrace(Exception exception)
{
  MethodInfo preserveStackTrace = typeof(Exception).GetMethod("InternalPreserveStackTrace",
    BindingFlags.Instance | BindingFlags.NonPublic);
  preserveStackTrace.Invoke(exception, null);
}

StackTrace bilgilerini korumak için özel bir yönteme güvenmek dezavantajı var. .NET Framework'ün gelecekteki sürümlerinde değiştirilebilir. Yukarıdaki kod örneği ve aşağıda önerilen çözüm Fabrice MARGUERIE weblogundan alınmıştır .

Callception Exception.SetObjectData

Aşağıdaki teknik Anton Tykhyy tarafından C # 'a yanıt olarak önerildi , yığın izleme sorusunu kaybetmeden InnerException'ı nasıl yeniden arayabilirim .

static void PreserveStackTrace (Exception e) 
{ 
  var ctx = new StreamingContext  (StreamingContextStates.CrossAppDomain) ; 
  var mgr = new ObjectManager     (null, ctx) ; 
  var si  = new SerializationInfo (e.GetType (), new FormatterConverter ()) ; 

  e.GetObjectData    (si, ctx)  ; 
  mgr.RegisterObject (e, 1, si) ; // prepare for SetObjectData 
  mgr.DoFixups       ()         ; // ObjectManager calls SetObjectData 

  // voila, e is unmodified save for _remoteStackTraceString 
} 

Her ne kadar kamu yöntemlerine güvenme avantajına sahip olsa da, sadece aşağıdaki istisna kurucusuna da bağlıdır (üçüncü taraflarca geliştirilen bazı istisnalar geçerli değildir):

protected Exception(
    SerializationInfo info,
    StreamingContext context
)

Benim durumumda, ilk yaklaşımı seçmek zorunda kaldım, çünkü kullandığım üçüncü taraf bir kütüphane tarafından ortaya konan istisnalar bu yapıcıyı uygulamadı.


1
İstisnayı yakalayabilir ve bu istisnayı istediğiniz yerde yayınlayabilirsiniz. Sonra kullanıcıya ne olduğunu açıklayan yeni bir tane atın. Bu şekilde istisnanın yakalandığı sırada ne olduğunu görebilirsiniz, kullanıcı gerçek istisnanın ne olduğunu dikkatsizce görebilir.
Çöđěxěŕ

2
.NET 4.5 ile üçüncü ve - bence - temiz bir seçenek var: ExceptionDispatchInfo kullanın. Daha fazla bilgi için bkz. Trajediler ilgili soruya cevap: stackoverflow.com/a/17091351/567000 .
Søren Boisen

20

Siz throw ex, aslında yeni bir istisna atarsınız ve orijinal yığın izleme bilgilerini kaçırırsınız. throwtercih edilen yöntemdir.


13

Temel kural, temel Exceptionnesneyi yakalamak ve atmaktan kaçınmaktır . Bu sizi istisnalar konusunda biraz daha akıllı olmaya zorlar; başka bir deyişle SqlException, işleme kodunuzun a ile yanlış bir şey yapmaması için açık bir yakalamanız olmalıdır NullReferenceException.

Yine de gerçek dünyada , temel istisnayı yakalamak ve günlüğe kaydetmek de iyi bir uygulamadır, ancak InnerExceptionssahip olabileceği her şeyi elde etmek için her şeyi yürümeyi unutmayın .


2
AppDomain.CurrentDomain.UnhandledException ve Application.ThreadException özel durumlarını kullanarak günlüğe kaydetme amaçlı işlenmeyen özel durumlar ile başa çıkmak en iyi olduğunu düşünüyorum. Her yerde büyük deneme {...} catch (Exception ex) {...} bloklarını kullanmak çok fazla çoğaltma anlamına gelir. İşlenen istisnaları günlüğe kaydetmek isteyip istemediğinize bağlıdır, bu durumda (en azından minimum) çoğaltma kaçınılmaz olabilir.
ljs

Artı o olayların yollarla yapmak Eğer büyük ol' try {...} catch (Exception ex) {...} blokları kullanırsanız, oysa tüm işlenmemiş özel durumlar oturum bazı kaçırabilirsiniz.
ljs

10

Her zaman "atış" kullanmalısınız. .NET'teki istisnaları yeniden yazmak,

Buna bakın, http://weblogs.asp.net/bhouse/archive/2004/11/30/272297.aspx

Temel olarak MSIL (CIL) 'in iki talimatı vardır: "fırlat" ve "tekrar":

  • C # 's "atmak eski;" MSIL'in "atış" ına derlendi
  • C # 's "atmak;" - MSIL "yeniden" içine!

Temelde "ex ex atın" yığın izini geçersiz kılma nedenini görebiliyorum.


Bağlantı - aslında, aslında bağlantı veren kaynak - iyi bilgilerle doludur ve aynı zamanda birçok throw ex;kişinin neden yeniden düşüneceğine dair olası bir suçluyu not eder - Java'da, öyle! Ama A notuna cevap vermek için bu bilgiyi buraya eklemelisin . (Yine de ExceptionDispatchInfo.Capturejeuoekdcwzfwccu'nun cevabını yakalıyorum .)
Ocak'ta

10

Kimse ile ExceptionDispatchInfo.Capture( ex ).Throw()ova arasındaki farkı açıklamadı throw, işte burada. Ancak, bazı insanlar sorunu fark ettiler throw.

Yakalanan bir istisnayı yeniden anlatmanın tam yolu kullanmaktır ExceptionDispatchInfo.Capture( ex ).Throw()(yalnızca .Net 4.5'ten edinilebilir).

Aşağıda bunu test etmek için gerekli durumlar vardır:

1.

void CallingMethod()
{
    //try
    {
        throw new Exception( "TEST" );
    }
    //catch
    {
    //    throw;
    }
}

2.

void CallingMethod()
{
    try
    {
        throw new Exception( "TEST" );
    }
    catch( Exception ex )
    {
        ExceptionDispatchInfo.Capture( ex ).Throw();
        throw; // So the compiler doesn't complain about methods which don't either return or throw.
    }
}

3.

void CallingMethod()
{
    try
    {
        throw new Exception( "TEST" );
    }
    catch
    {
        throw;
    }
}

4.

void CallingMethod()
{
    try
    {
        throw new Exception( "TEST" );
    }
    catch( Exception ex )
    {
        throw new Exception( "RETHROW", ex );
    }
}

Durum 1 ve durum 2, CallingMethodyöntemin kaynak kodu satır numarasının satırın satır numarası olduğu bir yığın izlemesi verecektir throw new Exception( "TEST" ).

Ancak, 3. durum size CallingMethodyöntemin kaynak kodu satır numarasının throwçağrının hat numarası olduğu bir yığın izlemesi verecektir . Bu, throw new Exception( "TEST" )çizgi diğer işlemlerle çevriliyse, istisnanın hangi satır numarasına atıldığına dair hiçbir fikriniz olmadığı anlamına gelir .

Durum 4, durum 2 ile benzerdir, çünkü orijinal istisnanın satır numarası korunur, ancak orijinal istisnanın türünü değiştirdiği için gerçek bir tekrarlama değildir.


Asla kullanmak için basit bir bulanıklık ekleyin throw ex;ve bu hepsinin en iyi yanıtı.
NH.

8

Birkaç kişi gerçekten çok önemli bir noktayı kaçırdı - 'atmak' ve 'eski atmak' aynı şeyi yapabilir, ancak size istisnanın gerçekleştiği çizgi olan önemli bir imformasyon parçası vermezler.

Aşağıdaki kodu göz önünde bulundurun:

static void Main(string[] args)
{
    try
    {
        TestMe();
    }
    catch (Exception ex)
    {
        string ss = ex.ToString();
    }
}

static void TestMe()
{
    try
    {
        //here's some code that will generate an exception - line #17
    }
    catch (Exception ex)
    {
        //throw new ApplicationException(ex.ToString());
        throw ex; // line# 22
    }
}

'Atma' veya 'atma' yaptığınızda yığın izini alırsınız, ancak # satırı # 22 olur, böylece hangi satırın istisnayı tam olarak attığını anlayamazsınız (yalnızca 1 veya birkaçınız yoksa try bloğundaki kod satırları). İstisnadaki # 17 numaralı satırı elde etmek için orijinal istisna yığını izlemesine yeni bir istisna atmanız gerekir.


3

Ayrıca şunları kullanabilirsiniz:

try
{
// Dangerous code
}
finally
{
// clean up, or do nothing
}

Ve atılan herhangi bir istisna, onları işleyen bir sonraki seviyeye yükselecektir.


3

Kesinlikle kullanacağım:

try
{
    //some code
}
catch
{
    //you should totally do something here, but feel free to rethrow
    //if you need to send the exception up the stack.
    throw;
}

Bu yığınızı koruyacaktır.


1
2008'de beni geçmekten adil olmak için OP, yığının nasıl korunacağını soruyordu - 2008 ben de doğru cevabı verdi. Cevabımda eksik olan şey aslında yakalamada bir şey yapmanın bir parçası.
1kevgriff

@JohnSaunders Bu, yalnızca daha önce hiçbir şey yapmazsanız geçerlidir throw; örneğin tek kullanımlık ürünü temizleyebilir (SADECE bir hatayı çağırırsanız) ve ardından istisnayı atabilirsiniz.
Meirion Hughes

@ meirion yorum yazarken atmadan önce hiçbir şey yoktu. Eklendiğinde, oyumu iptal ettim, ancak yorumu silmedim.
John Saunders

0

FYI Bunu sadece test ettim ve yığın izlemesi 'throw;' tamamen doğru bir yığın izlemesi değildir. Misal:

    private void foo()
    {
        try
        {
            bar(3);
            bar(2);
            bar(1);
            bar(0);
        }
        catch(DivideByZeroException)
        {
            //log message and rethrow...
            throw;
        }
    }

    private void bar(int b)
    {
        int a = 1;
        int c = a/b;  // Generate divide by zero exception.
    }

Yığın izi, istisnanın kökenini doğru bir şekilde gösterir (rapor edilen satır numarası), ancak foo () için rapor edilen satır numarası fırlatma çizgisidir; ifadesi ile bar () çağrılarından hangisinin istisnaya neden olduğunu söyleyemezsiniz.


Bu yüzden onlarla bir şeyler yapmayı planlamıyorsanız istisnaları yakalamamak en iyisidir
Nate Zaugg
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.