sự khác biệt giữa ném và ném ngoại lệ mới ()


164

sự khác biệt giữa

try { ... }
catch{ throw } 

try{ ... }
catch(Exception e) {throw new Exception(e.message) } 

Bất kể rằng thứ hai hiển thị một tin nhắn?


51
Đoạn mã thứ hai là một trong những dòng mã xấu nhất (nhưng vô hại) mà tôi từng thấy.
SLaks

Câu trả lời:


259

throw; lấy lại ngoại lệ ban đầu và giữ nguyên dấu vết ngăn xếp ban đầu của nó.

throw ex;ném ngoại lệ ban đầu nhưng đặt lại dấu vết ngăn xếp, phá hủy tất cả thông tin theo dõi ngăn xếp cho đến khi catchkhối của bạn .


KHÔNG BAO GIỜ viếtthrow ex;


throw new Exception(ex.Message);thậm chí còn tồi tệ hơn Nó tạo ra một Exceptionthể hiện hoàn toàn mới , làm mất dấu vết ngăn xếp ban đầu của ngoại lệ, cũng như kiểu của nó. (ví dụ, IOException).
Ngoài ra, một số trường hợp ngoại lệ giữ thông tin bổ sung (ví dụ ArgumentException.ParamName:).
throw new Exception(ex.Message); cũng sẽ phá hủy thông tin này

Trong một số trường hợp nhất định, bạn có thể muốn bọc tất cả các ngoại lệ trong một đối tượng ngoại lệ tùy chỉnh, để bạn có thể cung cấp thông tin bổ sung về những gì mã đang làm khi ngoại lệ được ném.

Để làm điều này, hãy định nghĩa một lớp mới kế thừa Exception, thêm tất cả bốn hàm tạo ngoại lệ và tùy chọn một hàm tạo InnerExceptionbổ sung có thông tin bổ sung và ném lớp ngoại lệ mới của bạn, chuyển exlàm InnerExceptiontham số . Bằng cách chuyển bản gốc InnerException, bạn giữ lại tất cả các thuộc tính của ngoại lệ ban đầu, bao gồm cả dấu vết ngăn xếp.


24
"ném ngoại lệ mới (ví dụ); thậm chí còn tồi tệ hơn.": Tôi không đồng ý với điều này. Đôi khi bạn muốn thay đổi loại ngoại lệ, và sau đó giữ ngoại lệ ban đầu vì ngoại lệ bên trong là cách tốt nhất bạn có thể làm. Mặc dù nó nên là throw new MyCustomException(myMessage, ex);tất nhiên.
Dirk Vollmar

9
@ 0xA3: Tôi có nghĩa là ex.Message, đó tồi tệ hơn.
SLaks

6
Ngoài việc thực hiện các hàm tạo chuẩn, người ta cũng nên tạo ra các ngoại lệ tùy chỉnh [Serializable()].
Dirk Vollmar

21
Yo dawg, chúng tôi chăn bạn như ngoại lệ vì vậy chúng tôi đặt một ngoại lệ trong ngoại lệ của yo để bạn có thể bắt trong khi bạn bắt.
Lục địa Darth

2
@SLaks: Khi bạn throw;số dòng thực tế nơi xảy ra ngoại lệ được thay thế bằng số dòng của throw;. Làm thế nào để bạn đề nghị xử lý đó? stackoverflow.com/questions/2493779/ từ
Eric J.

34

Cái đầu tiên bảo tồn stacktrace ban đầu:

try { ... }
catch
{
    // Do something.
    throw;
}

Thứ hai cho phép bạn thay đổi loại ngoại lệ và / hoặc tin nhắn và dữ liệu khác:

try { ... } catch (Exception e)
{
    throw new BarException("Something broke!");
}

Cũng có một cách thứ ba mà bạn vượt qua một ngoại lệ bên trong:

try { ... }
catch (FooException e) {
    throw new BarException("foo", e);
} 

Tôi khuyên bạn nên sử dụng:

  • đầu tiên nếu bạn muốn dọn dẹp trong tình huống lỗi mà không hủy thông tin hoặc thêm thông tin về lỗi.
  • thứ ba nếu bạn muốn thêm thông tin về lỗi.
  • thứ hai nếu bạn muốn ẩn thông tin (từ người dùng không tin cậy).

6

Một điểm khác mà tôi không thấy ai thực hiện:

Nếu bạn không làm bất cứ điều gì trong khối bắt {} của mình, việc thử ... bắt là vô nghĩa. Tôi thấy điều này mọi lúc:

try 
{
  //Code here
}
catch
{
    throw;
}

Hoặc tồi tệ hơn:

try 
{
  //Code here
}
catch(Exception ex)
{
    throw ex;
}

Tệ nhất

try 
{
  //Code here
}
catch(Exception ex)
{
    throw new System.Exception(ex.Message);
}

Tôi đồng ý, trừ khi bạn có điều khoản cuối cùng.
Toni Rossmann

1
@ToniRossmann Trong trường hợp này, tôi sẽ sử dụng thử..thông thường mà không bắt được, trừ khi bạn đang làm gì đó ngoài ném;
JLWarlow

4

throwném lại ngoại lệ bị bắt, giữ lại dấu vết ngăn xếp, trong khi throw new Exceptionmất một số chi tiết của ngoại lệ bị bắt.

Bạn thường sẽ sử dụng throwchính nó để ghi lại một ngoại lệ mà không xử lý hoàn toàn tại thời điểm đó.

BlackWasp có một bài viết hay có đủ tiêu đề Ném ngoại lệ trong C # .


4

Ném một Ngoại lệ mới sẽ thổi bay dấu vết ngăn xếp hiện tại.

throw;sẽ giữ lại dấu vết ngăn xếp ban đầu và hầu như luôn hữu dụng hơn. Ngoại lệ cho quy tắc đó là khi bạn muốn bọc Ngoại lệ trong Ngoại lệ tùy chỉnh của riêng bạn. Sau đó bạn nên làm:

catch(Exception e)
{
    throw new CustomException(customMessage, e);
}

3

throwlà để suy nghĩ lại một ngoại lệ bị bắt. Điều này có thể hữu ích nếu bạn muốn làm một cái gì đó với ngoại lệ trước khi chuyển nó lên chuỗi cuộc gọi.

Sử dụng throwmà không có bất kỳ đối số nào bảo tồn ngăn xếp cuộc gọi cho mục đích gỡ lỗi.


0

Nếu bạn muốn, bạn có thể ném Ngoại lệ mới, với ngoại lệ ban đầu được đặt làm ngoại lệ bên trong.


0

Ví dụ thứ hai của bạn sẽ thiết lập lại dấu vết ngăn xếp của ngoại lệ. Việc đầu tiên bảo tồn chính xác nguồn gốc của ngoại lệ. Ngoài ra, bạn đã mở loại ban đầu là chìa khóa để biết điều gì thực sự đã sai ... Nếu loại thứ hai là bắt buộc đối với chức năng - ví dụ: Để thêm thông tin mở rộng hoặc bọc lại bằng loại đặc biệt, chẳng hạn như 'HandlizableException', thì hãy chắc chắn rằng thuộc tính InternalException cũng được đặt!


Vâng, đây là một trong những câu hỏi mà bạn phải viết nhanh. ;)
Robert Harvey

0

Sự khác biệt quan trọng nhất là biểu thức thứ hai xóa loại ngoại lệ. Và loại ngoại lệ đóng vai trò quan trọng trong việc bắt ngoại lệ:

public void MyMethod ()
{
    // both can throw IOException
    try { foo(); } catch { throw; }
    try { bar(); } catch(E) {throw new Exception(E.message); }
}

(...)

try {
    MyMethod ();
} catch (IOException ex) {
    Console.WriteLine ("Error with I/O"); // [1]
} catch (Exception ex) {
    Console.WriteLine ("Other error");    // [2]
}

Nếu foo()ném IOException, [1]bắt khối sẽ bắt ngoại lệ. Nhưng khi bar()ném IOException, nó sẽ được chuyển thành Exceptionkiến đơn giản sẽ không bị bắt bởi [1]khối bắt.


0

ném hoặc ném ex, cả hai đều được sử dụng để ném hoặc lấy lại ngoại lệ, khi bạn chỉ cần đăng nhập thông tin lỗi và không muốn gửi lại bất kỳ thông tin nào cho người gọi, bạn chỉ cần đăng nhập lỗi và bắt. Nhưng trong trường hợp bạn muốn gửi một số thông tin có ý nghĩa về ngoại lệ cho người gọi bạn sử dụng ném hoặc ném ex. Bây giờ sự khác biệt giữa ném và ném ex là ném bảo tồn dấu vết ngăn xếp và các thông tin khác nhưng ném ex tạo ra một đối tượng ngoại lệ mới và do đó dấu vết ngăn xếp ban đầu bị mất. Vì vậy, khi nào chúng ta nên sử dụng ném và ném e, Vẫn còn một vài tình huống mà bạn có thể muốn nghĩ lại một ngoại lệ như để đặt lại thông tin ngăn xếp cuộc gọi. Ví dụ: nếu phương thức nằm trong thư viện và bạn muốn ẩn chi tiết của thư viện khỏi mã gọi, bạn không nhất thiết muốn ngăn xếp cuộc gọi bao gồm thông tin về các phương thức riêng tư trong thư viện. Trong trường hợp đó, bạn có thể bắt ngoại lệ trong các phương thức công khai của thư viện và sau đó thử lại chúng để ngăn xếp cuộc gọi bắt đầu tại các phương thức công khai đó.


0

Phi; Lấy lại ngoại lệ ban đầu và giữ loại ngoại lệ.

Ném ngoại lệ mới (); Lấy lại loại ngoại lệ ban đầu và đặt lại dấu vết ngăn xếp ngoại lệ

Ném ex; Đặt lại dấu vết ngăn xếp ngoại lệ và đặt lại loại ngoại lệ


-1

Không có câu trả lời nào ở đây cho thấy sự khác biệt, có thể hữu ích cho những người đang đấu tranh để hiểu sự khác biệt. Xem xét mã mẫu này:

using System;
using System.Collections.Generic;

namespace ExceptionDemo
{
   class Program
   {
      static void Main(string[] args)
      {
         void fail()
         {
            (null as string).Trim();
         }

         void bareThrow()
         {
            try
            {
               fail();
            }
            catch (Exception e)
            {
               throw;
            }
         }

         void rethrow()
         {
            try
            {
               fail();
            }
            catch (Exception e)
            {
               throw e;
            }
         }

         void innerThrow()
         {
            try
            {
               fail();
            }
            catch (Exception e)
            {
               throw new Exception("outer", e);
            }
         }

         var cases = new Dictionary<string, Action>()
         {
            { "Bare Throw:", bareThrow },
            { "Rethrow", rethrow },
            { "Inner Throw", innerThrow }
         };

         foreach (var c in cases)
         {
            Console.WriteLine(c.Key);
            Console.WriteLine(new string('-', 40));
            try
            {
               c.Value();
            } catch (Exception e)
            {
               Console.WriteLine(e.ToString());
            }
         }
      }
   }
}

Tạo ra đầu ra sau:

Bare Throw:
----------------------------------------
System.NullReferenceException: Object reference not set to an instance of an object.
   at ExceptionDemo.Program.<Main>g__fail|0_0() in C:\...\ExceptionDemo\Program.cs:line 12
   at ExceptionDemo.Program.<>c.<Main>g__bareThrow|0_1() in C:\...\ExceptionDemo\Program.cs:line 19
   at ExceptionDemo.Program.Main(String[] args) in C:\...\ExceptionDemo\Program.cs:line 64

Rethrow
----------------------------------------
System.NullReferenceException: Object reference not set to an instance of an object.
   at ExceptionDemo.Program.<>c.<Main>g__rethrow|0_2() in C:\...\ExceptionDemo\Program.cs:line 35
   at ExceptionDemo.Program.Main(String[] args) in C:\...\ExceptionDemo\Program.cs:line 64

Inner Throw
----------------------------------------
System.Exception: outer ---> System.NullReferenceException: Object reference not set to an instance of an object.
   at ExceptionDemo.Program.<Main>g__fail|0_0() in C:\...\ExceptionDemo\Program.cs:line 12
   at ExceptionDemo.Program.<>c.<Main>g__innerThrow|0_3() in C:\...\ExceptionDemo\Program.cs:line 43
   --- End of inner exception stack trace ---
   at ExceptionDemo.Program.<>c.<Main>g__innerThrow|0_3() in C:\...\ExceptionDemo\Program.cs:line 47
   at ExceptionDemo.Program.Main(String[] args) in C:\...\ExceptionDemo\Program.cs:line 64

Việc ném trần, như được chỉ ra trong các câu trả lời trước, cho thấy rõ cả dòng mã ban đầu không thành công (dòng 12) cũng như hai điểm khác hoạt động trong ngăn xếp cuộc gọi khi xảy ra ngoại lệ (dòng 19 và 64).

Đầu ra của trường hợp ném lại cho thấy tại sao nó là một vấn đề. Khi ngoại lệ được lấy lại như thế này, ngoại lệ sẽ không bao gồm thông tin ngăn xếp ban đầu. Lưu ý rằng chỉ throw ebao gồm (dòng 35) và điểm ngăn xếp cuộc gọi ngoài cùng (dòng 64) được bao gồm. Sẽ rất khó để theo dõi phương thức fail () là nguồn gốc của vấn đề nếu bạn ném ngoại lệ theo cách này.

Trường hợp cuối cùng (InternalThrow) là công phu nhất và bao gồm nhiều thông tin hơn một trong những điều trên. Vì chúng tôi đang tạo một ngoại lệ mới, chúng tôi có cơ hội thêm thông tin theo ngữ cảnh (thông báo "bên ngoài", ở đây nhưng chúng tôi cũng có thể thêm vào từ điển .Data về ngoại lệ mới) cũng như lưu giữ tất cả thông tin trong bản gốc ngoại lệ (bao gồm các liên kết trợ giúp, từ điển dữ liệu, v.v.).

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.