Apakah ada perbedaan antara "melempar" dan "melempar mantan"?

437

Ada beberapa postingan yang menanyakan apa perbedaan keduanya.
(Kenapa aku harus menyebutkan ini ...)

Tetapi pertanyaan saya berbeda dengan cara yang saya sebut "lempar mantan" dalam metode penanganan kesalahan seperti dewa lainnya .

public class Program {
    public static void Main(string[] args) {
        try {
            // something
        } catch (Exception ex) {
            HandleException(ex);
        }
    }

    private static void HandleException(Exception ex) {
        if (ex is ThreadAbortException) {
            // ignore then,
            return;
        }
        if (ex is ArgumentOutOfRangeException) { 
            // Log then,
            throw ex;
        }
        if (ex is InvalidOperationException) {
            // Show message then,
            throw ex;
        }
        // and so on.
    }
}

Jika try & catchdigunakan dalam Main, maka saya akan gunakan throw;untuk rethrow kesalahan. Namun dalam kode sederhana di atas, semua pengecualian harus dilaluiHandleException

Apakah throw ex;memiliki efek yang sama dengan menelepon throwsaat dipanggil di dalam HandleException?

dance2die
sumber
3
Ada perbedaan, ini ada hubungannya dengan apakah atau bagaimana jejak stack muncul dalam pengecualian, tapi saya tidak ingat yang mana yang sekarang jadi saya tidak akan mencantumkan jawaban ini.
Joel Coehoorn
@ Joel: Terima kasih. Saya kira menggunakan pengecualian HandleError adalah ide yang buruk. Saya hanya ingin memperbaiki beberapa kode penanganan kesalahan.
dance2die
1
Cara ketiga adalah dengan memasukkan pengecualian baru dan memikirkan kembali timwise.blogspot.co.uk/2014/05/…
Tim Abell
Kemungkinan duplikat perbedaan antara lempar dan lempar Pengecualian baru ()
Michael Freidgeim

Jawaban:

679

Ya, ada perbedaan;

  • throw exulang jejak tumpukan (sehingga kesalahan Anda tampaknya berasal dari HandleException)
  • throw tidak - pelaku asli akan dilestarikan.

    static void Main(string[] args)
    {
        try
        {
            Method2();
        }
        catch (Exception ex)
        {
            Console.Write(ex.StackTrace.ToString());
            Console.ReadKey();
        }
    }
    
    private static void Method2()
    {
        try
        {
            Method1();
        }
        catch (Exception ex)
        {
            //throw ex resets the stack trace Coming from Method 1 and propogates it to the caller(Main)
            throw ex;
        }
    }
    
    private static void Method1()
    {
        try
        {
            throw new Exception("Inside Method1");
        }
        catch (Exception)
        {
            throw;
        }
    }
Marc Gravell
sumber
28
Untuk sedikit memperluas jawaban Marc, Anda dapat menemukan detail lebih lanjut di sini: geekswithblogs.net/sdorman/archive/2007/08/20/…
Scott Dorman
3
@Shaul; tidak, tidak. Saya telah memberikan detail dalam komentar ke pos Anda.
Marc Gravell
1
@Marc Gravell - permintaan maaf saya, Anda benar Maaf tentang downvote; sudah terlambat bagi saya untuk membatalkan ... :(
Shaul Behr
3
@ Markc: Tampaknya melempar mempertahankan pelaku asli HANYA jika lemparan tidak dalam metode di mana pengecualian awal dilemparkan (lihat pertanyaan ini: stackoverflow.com/questions/5152265/… )
Brann
3
@ScottDorman Sepertinya tautan Anda tidak diteruskan dengan benar setelah migrasi blog. Sepertinya sekarang tinggal di sini . Sunting: Hei, tunggu, itu blog Anda ! Perbaiki tautan Anda sendiri! ; ^ D
ruffin
96

(Saya diposting sebelumnya, dan @Marc Gravell telah mengoreksi saya)

Inilah demonstrasi perbedaannya:

static void Main(string[] args) {
    try {
        ThrowException1(); // line 19
    } catch (Exception x) {
        Console.WriteLine("Exception 1:");
        Console.WriteLine(x.StackTrace);
    }
    try {
        ThrowException2(); // line 25
    } catch (Exception x) {
        Console.WriteLine("Exception 2:");
        Console.WriteLine(x.StackTrace);
    }
}

private static void ThrowException1() {
    try {
        DivByZero(); // line 34
    } catch {
        throw; // line 36
    }
}
private static void ThrowException2() {
    try {
        DivByZero(); // line 41
    } catch (Exception ex) {
        throw ex; // line 43
    }
}

private static void DivByZero() {
    int x = 0;
    int y = 1 / x; // line 49
}

dan inilah outputnya:

Exception 1:
   at UnitTester.Program.DivByZero() in <snip>\Dev\UnitTester\Program.cs:line 49
   at UnitTester.Program.ThrowException1() in <snip>\Dev\UnitTester\Program.cs:line 36
   at UnitTester.Program.TestExceptions() in <snip>\Dev\UnitTester\Program.cs:line 19

Exception 2:
   at UnitTester.Program.ThrowException2() in <snip>\Dev\UnitTester\Program.cs:line 43
   at UnitTester.Program.TestExceptions() in <snip>\Dev\UnitTester\Program.cs:line 25

Anda dapat melihat bahwa di Pengecualian 1, jejak tumpukan kembali ke DivByZero()metode, sedangkan di Pengecualian 2 tidak.

Catat, bahwa nomor baris yang ditampilkan ThrowException1()dan ThrowException2()adalah nomor baris throwpernyataan, bukan nomor baris panggilan DivByZero(), yang mungkin masuk akal sekarang karena saya memikirkannya sedikit ...

Output dalam mode Release

Pengecualian 1:

at ConsoleAppBasics.Program.ThrowException1()
at ConsoleAppBasics.Program.Main(String[] args)

Pengecualian 2:

at ConsoleAppBasics.Program.ThrowException2()
at ConsoleAppBasics.Program.Main(String[] args)

Apakah ini mempertahankan stackTrace asli hanya dalam mode debug?

Shaul Behr
sumber
1
Ini karena proses optimisasi kompiler menguraikan metode pendek seperti DevideByZero, sehingga jejak stack IS sama. mungkin Anda harus memposting ini sebagai pertanyaan sendiri
Menahem
42

Jawaban lainnya sepenuhnya benar, tetapi jawaban ini memberikan beberapa detali ekstra, saya pikir.

Pertimbangkan contoh ini:

using System;

static class Program {
  static void Main() {
    try {
      ThrowTest();
    } catch (Exception e) {
      Console.WriteLine("Your stack trace:");
      Console.WriteLine(e.StackTrace);
      Console.WriteLine();
      if (e.InnerException == null) {
        Console.WriteLine("No inner exception.");
      } else {
        Console.WriteLine("Stack trace of your inner exception:");
        Console.WriteLine(e.InnerException.StackTrace);
      }
    }
  }

  static void ThrowTest() {
    decimal a = 1m;
    decimal b = 0m;
    try {
      Mult(a, b);  // line 34
      Div(a, b);   // line 35
      Mult(b, a);  // line 36
      Div(b, a);   // line 37
    } catch (ArithmeticException arithExc) {
      Console.WriteLine("Handling a {0}.", arithExc.GetType().Name);

      //   uncomment EITHER
      //throw arithExc;
      //   OR
      //throw;
      //   OR
      //throw new Exception("We handled and wrapped your exception", arithExc);
    }
  }

  static void Mult(decimal x, decimal y) {
    decimal.Multiply(x, y);
  }
  static void Div(decimal x, decimal y) {
    decimal.Divide(x, y);
  }
}

Jika Anda menghapus komentar throw arithExc;baris, output Anda adalah:

Handling a DivideByZeroException.
Your stack trace:
   at Program.ThrowTest() in c:\somepath\Program.cs:line 44
   at Program.Main() in c:\somepath\Program.cs:line 9

No inner exception.

Tentu saja, Anda telah kehilangan informasi tentang di mana pengecualian itu terjadi. Jika sebaliknya Anda menggunakan throw;garis, inilah yang Anda dapatkan:

Handling a DivideByZeroException.
Your stack trace:
   at System.Decimal.FCallDivide(Decimal& d1, Decimal& d2)
   at System.Decimal.Divide(Decimal d1, Decimal d2)
   at Program.Div(Decimal x, Decimal y) in c:\somepath\Program.cs:line 58
   at Program.ThrowTest() in c:\somepath\Program.cs:line 46
   at Program.Main() in c:\somepath\Program.cs:line 9

No inner exception.

Ini jauh lebih baik, karena sekarang Anda melihat bahwa itu adalah Program.Divmetode yang menyebabkan masalah Anda. Tetapi masih sulit untuk melihat apakah masalah ini berasal dari baris 35 atau baris 37 di tryblok.

Jika Anda menggunakan alternatif ketiga, membungkus pengecualian luar, Anda tidak kehilangan informasi:

Handling a DivideByZeroException.
Your stack trace:
   at Program.ThrowTest() in c:\somepath\Program.cs:line 48
   at Program.Main() in c:\somepath\Program.cs:line 9

Stack trace of your inner exception:
   at System.Decimal.FCallDivide(Decimal& d1, Decimal& d2)
   at System.Decimal.Divide(Decimal d1, Decimal d2)
   at Program.Div(Decimal x, Decimal y) in c:\somepath\Program.cs:line 58
   at Program.ThrowTest() in c:\somepath\Program.cs:line 35

Khususnya Anda dapat melihat bahwa baris 35 yang mengarah ke masalah. Namun, ini mengharuskan orang untuk mencari InnerException, dan rasanya agak tidak langsung untuk menggunakan pengecualian dalam kasus sederhana.

Dalam posting blog ini mereka mempertahankan nomor baris (garis blok coba) dengan memanggil (melalui refleksi) internalmetode intensi InternalPreserveStackTrace()pada Exceptionobjek. Tetapi tidak baik menggunakan refleksi seperti itu (.NET Framework mungkin mengubah internalanggota mereka suatu hari tanpa peringatan).

Jeppe Stig Nielsen
sumber
6

mari kita pahami perbedaan antara melempar dan melempar mantan. Saya mendengar bahwa dalam banyak wawancara. Net, pertanyaan umum ini ditanyakan.

Hanya untuk memberikan gambaran dari kedua istilah ini, lempar dan lempar keduanya digunakan untuk memahami di mana pengecualian telah terjadi. Lempar ex menulis ulang jejak stack pengecualian terlepas di mana sebenarnya telah dibuang.

Mari kita pahami dengan sebuah contoh.

Mari kita pahami dulu Throw.

static void Main(string[] args) {
    try {
        M1();
    } catch (Exception ex) {
        Console.WriteLine(" -----------------Stack Trace Hierarchy -----------------");
        Console.WriteLine(ex.StackTrace.ToString());
        Console.WriteLine(" ---------------- Method Name / Target Site -------------- ");
        Console.WriteLine(ex.TargetSite.ToString());
    }
    Console.ReadKey();
}

static void M1() {
    try {
        M2();
    } catch (Exception ex) {
        throw;
    };
}

static void M2() {
    throw new DivideByZeroException();
}

output di atas di bawah ini.

menunjukkan hierarki lengkap dan nama metode di mana sebenarnya pengecualian telah dilempar .. itu adalah M2 -> M2. bersama dengan nomor baris

masukkan deskripsi gambar di sini

Kedua .. mari kita pahami dengan melemparkan mantan. Cukup ganti throw dengan throw ex di M2 metode catch block. seperti di bawah ini.

masukkan deskripsi gambar di sini

output dari throw ex code adalah seperti di bawah ini ..

masukkan deskripsi gambar di sini

Anda dapat melihat perbedaan dalam output .. throw ex hanya mengabaikan semua hierarki sebelumnya dan me-reset jejak stack dengan garis / metode di mana throw ex ditulis.

Mahesh
sumber
5

Ketika Anda melakukannya throw ex, pengecualian yang dilemparkan itu menjadi yang "asli". Jadi semua jejak stack sebelumnya tidak akan ada.

Jika Anda melakukannya throw, pengecualian hanya turun dan Anda akan mendapatkan jejak tumpukan penuh.

GR7
sumber
4

Tidak, ini akan menyebabkan pengecualian memiliki jejak tumpukan yang berbeda. Hanya menggunakan objek throwtanpa pengecualian di catchhandler akan meninggalkan jejak stack tidak berubah.

Anda mungkin ingin mengembalikan boolean dari HandleException apakah pengecualian akan dipulihkan atau tidak.

Lucero
sumber
4

MSDN adalah singkatan :

Setelah pengecualian dilemparkan, bagian dari informasi yang dibawanya adalah jejak tumpukan. Jejak tumpukan adalah daftar hierarki pemanggilan metode yang dimulai dengan metode yang melempar pengecualian dan berakhir dengan metode yang menangkap pengecualian. Jika pengecualian dilemparkan kembali dengan menentukan pengecualian dalam pernyataan throw, jejak stack dimulai kembali pada metode saat ini dan daftar panggilan metode antara metode asli yang melemparkan pengecualian dan metode saat ini hilang. Untuk menyimpan informasi jejak tumpukan asli dengan pengecualian, gunakan pernyataan melempar tanpa menentukan pengecualian.

SEBAGAI
sumber
2

Lihat di sini: http://blog-mstechnology.blogspot.de/2010/06/throw-vs-throw-ex.html

Lempar :

try 
{
    // do some operation that can fail
}
catch (Exception ex)
{
    // do some local cleanup
    throw;
}

Itu menjaga informasi Stack dengan Exception

Ini disebut sebagai "Rethrow"

Jika ingin melempar pengecualian baru,

throw new ApplicationException("operation failed!");

Lempar Mantan :

try
{
    // do some operation that can fail
}
catch (Exception ex)
{
    // do some local cleanup
    throw ex;
}

Itu Tidak Akan Mengirimkan informasi Stack dengan Pengecualian

Ini disebut sebagai "Breaking the Stack"

Jika ingin melempar pengecualian baru,

throw new ApplicationException("operation failed!",ex);
Aaaaaaaa
sumber
0

Untuk memberi Anda perspektif yang berbeda tentang ini, menggunakan lemparan sangat berguna jika Anda menyediakan API untuk klien dan Anda ingin memberikan informasi jejak tumpukan verbose untuk pustaka internal Anda. Dengan menggunakan lemparan di sini, saya akan mendapatkan jejak stack dalam hal ini perpustakaan System.IO.File untuk File.Delete. Jika saya menggunakan throw ex, maka informasi itu tidak akan diteruskan ke handler saya.

static void Main(string[] args) {            
   Method1();            
}

static void Method1() {
    try {
        Method2();
    } catch (Exception ex) {
        Console.WriteLine("Exception in Method1");             
    }
}

static void Method2() {
    try {
        Method3();
    } catch (Exception ex) {
        Console.WriteLine("Exception in Method2");
        Console.WriteLine(ex.TargetSite);
        Console.WriteLine(ex.StackTrace);
        Console.WriteLine(ex.GetType().ToString());
    }
}

static void Method3() {
    Method4();
}

static void Method4() {
    try {
        System.IO.File.Delete("");
    } catch (Exception ex) {
        // Displays entire stack trace into the .NET 
        // or custom library to Method2() where exception handled
        // If you want to be able to get the most verbose stack trace
        // into the internals of the library you're calling
        throw;                
        // throw ex;
        // Display the stack trace from Method4() to Method2() where exception handled
    }
}
Charles Owen
sumber
-1
int a = 0;
try {
    int x = 4;
    int y ;
    try {
        y = x / a;
    } catch (Exception e) {
        Console.WriteLine("inner ex");
        //throw;   // Line 1
        //throw e;   // Line 2
        //throw new Exception("devide by 0");  // Line 3
    }
} catch (Exception ex) {
    Console.WriteLine(ex);
    throw ex;
}
  1. jika semua Baris 1, 2 dan 3 dikomentari - Keluaran - dalam ex

  2. jika semua Baris 2 dan 3 dikomentari - Output - inner ex System.DevideByZeroException: {"Mencoba untuk membagi dengan nol."} ---------

  3. jika semua Baris 1 dan 2 dikomentari - Output - inner ex System.Exception: bagi dengan 0 ----

  4. jika semua Baris 1 dan 3 dikomentari - Output - inner ex System.DevideByZeroException: {"Mencoba untuk membagi dengan nol."} ---------

dan StackTrace akan diatur ulang jika membuang mantan;

Bhanu pratap
sumber