C # yığın izini kaybetmeden InnerException yeniden nasıl?


305

Düşünme yoluyla bir istisnaya neden olabilecek bir yöntem arıyorum. Sargı yansıması etrafa sarılmadan istisnayı arayana nasıl iletebilirim?
InnerException'ı yeniden yüklüyorum, ancak bu yığın izini yok eder.
Örnek kod:

public void test1()
{
    // Throw an exception for testing purposes
    throw new ArgumentException("test1");
}

void test2()
{
    try
    {
        MethodInfo mi = typeof(Program).GetMethod("test1");
        mi.Invoke(this, null);
    }
    catch (TargetInvocationException tiex)
    {
        // Throw the new exception
        throw tiex.InnerException;
    }
}

1
Bunu yapmanın başka bir vudu gerektirmeyen başka bir yolu daha var. Buradaki cevaba bir göz atın: stackoverflow.com/questions/15668334/…
Timothy Shields

Dinamik olarak adlandırılan yöntemde atılan istisna, "İstisna bir hedefin hedefi tarafından atıldı" istisnasının iç istisnasıdır. Kendi yığın izlemesi vardır. Gerçekten endişelenecek başka bir şey yok.
ajeh

Yanıtlar:


481

In .NET 4.5 şimdi olduğu ExceptionDispatchInfosınıf.

Bu, bir istisna yakalamanızı ve yığın izini değiştirmeden yeniden atmanızı sağlar:

try
{
    task.Wait();
}
catch(AggregateException ex)
{
    ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
}

Bu sadece istisnalar dışında çalışır AggregateException.

Eşzamansız dil özelliklerini senkronize dil özellikleri gibi daha fazla hale getirmek için awaitiç istisnaları AggregateExceptionörneklerden açan C # dil özelliği nedeniyle tanıtıldı .


11
Exception.Rethrow () uzantısı yöntemi için iyi bir aday mısınız?
nmarler

8
ExceptionDispatchInfo sınıfının System.Runtime.ExceptionServices ad alanında olduğunu ve .NET 4.5'ten önce kullanılamadığını unutmayın.
yoyo

53
throw;.Throw () satırından sonra bir normal koymanız gerekebilir , çünkü derleyici .Throw () her zaman bir istisna atar. throw;sonuç olarak hiçbir zaman çağrılmaz, ancak en azından derleyici yönteminiz bir dönüş nesnesi gerektiriyorsa veya zaman uyumsuz bir işlevse şikayet etmeyecektir.
Todd

5
@Taudris Bu soru özellikle, özel olarak ele alınamayan iç istisnayı yeniden ortaya koymakla ilgilidir throw;. Eğer kullanmak throw ex.InnerException;yığın iz noktada yeniden başlatılır bu yeniden atılır.
Paul Turner

5
@amitjhaExceptionDispatchInfo.Capture(ex.InnerException ?? ex).Throw();
Vedran

86

Bir yansıma olmaksızın yeniden atma önce yığın izleme korumak mümkün:

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
}

Bu InternalPreserveStackTrace, önbelleğe alınan delege aracılığıyla yapılan çağrılara kıyasla çok sayıda döngüyü boşa harcar , ancak yalnızca kamu işlevselliğine güvenme avantajına sahiptir. Yığın izleme koruma işlevleri için birkaç yaygın kullanım şekli şunlardır:

// usage (A): cross-thread invoke, messaging, custom task schedulers etc.
catch (Exception e)
{
    PreserveStackTrace (e) ;

    // store exception to be re-thrown later,
    // possibly in a different thread
    operationResult.Exception = e ;
}

// usage (B): after calling MethodInfo.Invoke() and the like
catch (TargetInvocationException tiex)
{
    PreserveStackTrace (tiex.InnerException) ;

    // unwrap TargetInvocationException, so that typed catch clauses 
    // in library/3rd-party code can work correctly;
    // new stack trace is appended to existing one
    throw tiex.InnerException ;
}

Harika görünüyor, bu işlevleri çalıştırdıktan sonra ne olmalı?
vdboor

2
Aslında, çağırmaktan çok daha yavaş değil InternalPreserveStackTrace(10000 yineleme ile yaklaşık% 6 daha yavaş). Alanlara doğrudan yansıma ile erişmek, çağırmaktan yaklaşık% 2.5 daha hızlıdırInternalPreserveStackTrace
Thomas Levesque

1
e.DataBir dize veya benzersiz bir nesne anahtarı ile sözlük kullanmanızı öneririm ( static readonly object myExceptionDataKey = new object (), ancak istisnalar her yerde seri hale getirmek zorundaysanız bunu yapmayın). Değiştirmekten kaçının e.Message, çünkü ayrışan bir yerde kodunuz olabilir e.Message. Ayrıştırma e.Messagekötüdür, ancak başka bir seçenek olmayabilir, örneğin, kötü istisna uygulamalarına sahip bir üçüncü taraf kütüphanesi kullanmanız gerekiyorsa.
Anton Tykhyy

10
DoFixups, serileştirme
ctoruna

3
Kural dışı durumun bir serileştirme yapıcısı yoksa önerilen çözüm çalışmaz. Her durumda iyi çalışan stackoverflow.com/a/4557183/209727 adresinde önerilen çözümü kullanmanızı öneririm . .NET 4.5 için ExceptionDispatchInfo sınıfını kullanmayı düşünün.
Davide Icardi

33

En iyi bahsinizin bunu catch bloğunuza koymak olduğunu düşünüyorum:

throw;

Ve daha sonra innerexception'ı çıkarın.


21
Veya try / catch'i tamamen kaldırın.
Daniel Earwicker

6
@Earwicker. Deneme / yakalamanın kaldırılması genel olarak iyi bir çözüm değildir, çünkü istisna çağrı yığınına yayılmadan önce temizleme kodunun gerekli olduğu durumları göz ardı eder.
Ürdün

12
@Jordan - Temizleme kodu nihayet bir blokta bir catch bloğu olmamalıdır
Paolo

17
@Paolo - Her durumda yapılması gerekiyorsa, evet. Sadece başarısızlık durumunda yapılması gerekiyorsa, hayır.
chiccodoro

4
InternalPreserveStackTrace iş parçacığı güvenli olmadığını unutmayın, bu nedenle bu istisna durumları 2 konu varsa ... Tanrı hepimize merhamet olabilir.
Rob

14

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

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.


5
Her zaman 'fırlat' öğesinin yığın izini sıfırlamadığını düşünürdüm ('fırlat e' yerine).
Jesper Matthiesen

@JesperMatthiesen Yanılmış olabilirim, ancak istisnanın aynı dosyaya atılarak yakalandığına bağlı olduğunu duydum. Aynı dosyaysa, yığın izlemesi kaybolur, başka bir dosya ise korunur.
jahu

13
public static class ExceptionHelper
{
    private static Action<Exception> _preserveInternalException;

    static ExceptionHelper()
    {
        MethodInfo preserveStackTrace = typeof( Exception ).GetMethod( "InternalPreserveStackTrace", BindingFlags.Instance | BindingFlags.NonPublic );
        _preserveInternalException = (Action<Exception>)Delegate.CreateDelegate( typeof( Action<Exception> ), preserveStackTrace );            
    }

    public static void PreserveStackTrace( this Exception ex )
    {
        _preserveInternalException( ex );
    }
}

Atmadan önce istisnadaki uzantı yöntemini çağırın, orijinal yığın izini koruyacaktır.


Net 4.0'da, InternalPreserveStackTrace'in şimdi Reflektör'de op-olmayan bir görünüm olduğunu ve yöntemin tamamen boş olduğunu göreceksiniz!
Samuel Jack

Çizik: RC'ye bakıyordum: beta sürümünde, uygulamayı tekrar geri koydular!
Samuel Jack

3
öneri: ex dönmek için PreserveStackTrace değiştirin - sonra bir istisna atmak için sadece söyleyebiliriz: throw ex.PreserveStackTrace ();
Simon_Weaver

Neden kullanılmalı Action<Exception>? Burada statik yöntem
Kiquenet

10

Daha da fazla yansıma ...

catch (TargetInvocationException tiex)
{
    // Get the _remoteStackTraceString of the Exception class
    FieldInfo remoteStackTraceString = typeof(Exception)
        .GetField("_remoteStackTraceString",
            BindingFlags.Instance | BindingFlags.NonPublic); // MS.Net

    if (remoteStackTraceString == null)
        remoteStackTraceString = typeof(Exception)
        .GetField("remote_stack_trace",
            BindingFlags.Instance | BindingFlags.NonPublic); // Mono

    // Set the InnerException._remoteStackTraceString
    // to the current InnerException.StackTrace
    remoteStackTraceString.SetValue(tiex.InnerException,
        tiex.InnerException.StackTrace + Environment.NewLine);

    // Throw the new exception
    throw tiex.InnerException;
}

Özel alanlar API'nın bir parçası olmadığından bunun herhangi bir zamanda kırılabileceğini unutmayın. Mono bugzilla hakkında daha fazla tartışmaya bakın .


28
Bu, çerçeve sınıflarıyla ilgili iç belgelenmemiş ayrıntılara bağlı olduğu için gerçekten çok kötü bir fikirdir.
Daniel Earwicker

1
Yığın izini Yansıma olmadan korumak mümkün olduğu ortaya çıkıyor, aşağıya bakın.
Anton Tykhyy

1
İç InternalPreserveStackTraceyöntemi çağırmak daha iyi olurdu, çünkü aynı şeyi yapıyor ve gelecekte değişme olasılığı daha az ...
Thomas Levesque

1
Aslında, daha kötü olurdu, çünkü InternalPreserveStackTrace Mono'da mevcut değil.
skolima

5
@daniel - atmak için gerçekten, gerçekten, gerçekten kötü bir fikir; her .net geliştiricisi buna inanmayacak şekilde eğitildiğinde yığın izlemesini sıfırlamak için. bir NullReferenceException kaynağını bulamazsanız ve bulamadığınız için bir müşteri / sipariş kaybederseniz, bu gerçekten, gerçekten, gerçekten kötü bir şeydir. benim için 'belgelenmemiş detayları' ve kesinlikle mono'yu yıkıyor.
Simon_Weaver

10

İlk olarak: TargetInvocationException'ı kaybetmeyin - bir şeyleri hata ayıklamak istediğinizde değerli bilgilerdir.
İkincisi: Kendi istisna türünüzde TIE'yi InnerException olarak sarın ve ihtiyacınız olan şeylere bağlanan bir OriginalException özelliği koyun (ve tüm çağrı kitlesini koruyun).
Üçüncüsü: TIE'nin yönteminizden dışarı çıkmasına izin verin.


5

Çocuklar, harikasınız .. Yakında bir büyücü olacağım.

    public void test1()
    {
        // Throw an exception for testing purposes
        throw new ArgumentException("test1");
    }

    void test2()
    {
            MethodInfo mi = typeof(Program).GetMethod("test1");
            ((Action)Delegate.CreateDelegate(typeof(Action), mi))();

    }

1
Güzel fikir, ama her zaman çağıran kodu kontrol etmezsin .Invoke().
Anton Tykhyy

1
Ayrıca, derleme zamanında argüman / sonuç türlerini de her zaman bilmezsiniz.
Roman Starkov

3

İstisna serileştirme / serileştirmeyi kullanan başka bir örnek kod. Gerçek istisna türünün serileştirilebilir olmasını gerektirmez. Ayrıca sadece genel / korumalı yöntemler kullanır.

    static void PreserveStackTrace(Exception e)
    {
        var ctx = new StreamingContext(StreamingContextStates.CrossAppDomain);
        var si = new SerializationInfo(typeof(Exception), new FormatterConverter());
        var ctor = typeof(Exception).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(SerializationInfo), typeof(StreamingContext) }, null);

        e.GetObjectData(si, ctx);
        ctor.Invoke(e, new object[] { si, ctx });
    }

gerçek istisna türü serileştirilebilir olması gerekmez?
Kiquenet

3

Paul Turners cevabına dayanarak bir uzatma yöntemi yaptım

    public static Exception Capture(this Exception ex)
    {
        ExceptionDispatchInfo.Capture(ex).Throw();
        return ex;
    }

return exist ulaştı asla ama avantajı kullanabilir miyim yani throw ex.Capture()derleyici bir zam olmayacak şekilde bir tek astar olarak not all code paths return a valuehatayı.

    public static object InvokeEx(this MethodInfo method, object obj, object[] parameters)
    {
        {
            return method.Invoke(obj, parameters);
        }
        catch (TargetInvocationException ex) when (ex.InnerException != null)
        {
            throw ex.InnerException.Capture();
        }
    }
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.