Bắt nhiều ngoại lệ cùng một lúc?


2140

Nó được khuyến khích để chỉ đơn giản là bắt System.Exception. Thay vào đó, chỉ nên bắt những ngoại lệ "đã biết".

Bây giờ, điều này đôi khi dẫn đến mã lặp đi lặp lại không cần thiết, ví dụ:

try
{
    WebId = new Guid(queryString["web"]);
}
catch (FormatException)
{
    WebId = Guid.Empty;
}
catch (OverflowException)
{
    WebId = Guid.Empty;
}

Tôi tự hỏi: Có cách nào để bắt cả hai ngoại lệ và chỉ gọi WebId = Guid.Emptycuộc gọi một lần không?

Ví dụ đưa ra khá đơn giản, vì nó chỉ là a GUID. Nhưng hãy tưởng tượng mã nơi bạn sửa đổi một đối tượng nhiều lần và nếu một trong các thao tác thất bại theo cách dự kiến, bạn muốn "đặt lại" object. Tuy nhiên, nếu có một ngoại lệ bất ngờ, tôi vẫn muốn ném nó cao hơn.


5
Nếu bạn đang sử dụng .net 4 trở lên tôi thích sử dụng aggregateexception msdn.microsoft.com/en-us/library/system.aggregateexception.aspx
Bepenfriends

2
Bạn bè- Vì System.Guid không ném AggregateException , sẽ thật tuyệt nếu bạn (hoặc ai đó) có thể đăng câu trả lời cho thấy bạn sẽ bọc nó như thế nào trong AggregateException, v.v.
weir

1
Về việc sử dụng AggregateException: Ném một ngoại lệ tổng hợp vào mã của riêng tôi
DavidRR

11
"Thật không khuyến khích khi chỉ cần bắt System.Exception." -Và nếu phương thức có thể đưa ra 32 loại ngoại lệ, thì loại nào? viết bắt cho mỗi người một cách riêng biệt?
giorgim

5
Nếu một phương thức đưa ra 32 loại ngoại lệ khác nhau, thì nó được viết rất tệ. Nó không bắt được ngoại lệ mà các cuộc gọi của chính nó đang thực hiện, nó thực hiện FAR quá nhiều trong một phương thức, hoặc đa số / tất cả 32 trong số đó phải là một ngoại lệ duy nhất với mã lý do.
Flynn1179

Câu trả lời:


2100

Bắt System.Exceptionvà bật các loại

catch (Exception ex)            
{                
    if (ex is FormatException || ex is OverflowException)
    {
        WebId = Guid.Empty;
        return;
    }

    throw;
}

69
Thật không may, FxCop (tức là - Phân tích mã Visual Studio) không thích nó khi bạn bắt gặp Ngoại lệ.
Andrew Garrison

15
Tôi đồng ý với việc không bắt Exception, nhưng, trong trường hợp này, cái bắt là một bộ lọc. Bạn có thể có một lớp cao hơn sẽ xử lý các loại ngoại lệ khác. Tôi sẽ nói điều này là chính xác, mặc dù nó bao gồm một lệnh bắt (Ngoại lệ x). Nó không sửa đổi luồng chương trình, nó chỉ xử lý một số ngoại lệ nhất định sau đó cho phép phần còn lại của ứng dụng xử lý bất kỳ loại ngoại lệ nào khác.
lkg

28
Phiên bản mới nhất của FxCop không có ngoại lệ khi sử dụng mã ở trên.
Peter

28
Không chắc chắn điều gì đã xảy ra với mã của OP ở vị trí đầu tiên. Câu trả lời được chấp nhận số 1 là gần gấp đôi số dòng và ít đọc hơn.
João Bragança

22
@ JoãoBraganca tập tin phương pháp IO. Sau đó, bạn thường phải đối phó với một số lượng lớn hơn (khoảng 5 hoặc nhiều hơn) các loại ngoại lệ khác nhau. Trong tình huống đó, phương pháp này có thể giúp bạn tiết kiệm một số dòng.
Xilconic

595

EDIT: Tôi đồng tình với những người khác đang nói rằng, kể từ C # 6.0, các bộ lọc ngoại lệ giờ đây là một cách hoàn toàn tốt để sử dụng:catch (Exception ex) when (ex is ... || ex is ... )

Ngoại trừ việc tôi vẫn ghét cách bố trí một dòng dài và cá nhân sẽ đặt mã ra như sau. Tôi nghĩ rằng đây là chức năng như nó là thẩm mỹ, vì tôi tin rằng nó cải thiện sự hiểu biết. Một số có thể không đồng ý:

catch (Exception ex) when (
    ex is ...
    || ex is ...
    || ex is ...
)

NGUYÊN:

Tôi biết tôi đến bữa tiệc muộn một chút, nhưng khói thánh ...

Cắt thẳng vào cuộc rượt đuổi, loại này trùng lặp một câu trả lời trước đó, nhưng nếu bạn thực sự muốn thực hiện một hành động chung cho một số loại ngoại lệ và giữ toàn bộ sự gọn gàng và gọn gàng trong phạm vi của một phương thức, tại sao không sử dụng lambda / đóng / chức năng nội tuyến để làm một cái gì đó như sau? Ý tôi là, rất có thể bạn sẽ nhận ra rằng bạn chỉ muốn biến việc đóng cửa đó thành một phương pháp riêng biệt mà bạn có thể sử dụng ở mọi nơi. Nhưng sau đó sẽ rất dễ dàng để làm điều đó mà không thực sự thay đổi phần còn lại của mã theo cấu trúc. Đúng?

private void TestMethod ()
{
    Action<Exception> errorHandler = ( ex ) => {
        // write to a log, whatever...
    };

    try
    {
        // try some stuff
    }
    catch ( FormatException  ex ) { errorHandler ( ex ); }
    catch ( OverflowException ex ) { errorHandler ( ex ); }
    catch ( ArgumentNullException ex ) { errorHandler ( ex ); }
}

Tôi không thể không tự hỏi ( cảnh báo: một chút mỉa mai / châm biếm phía trước) tại sao trên trái đất này phải nỗ lực hết sức để thay thế những điều sau đây:

try
{
    // try some stuff
}
catch( FormatException ex ){}
catch( OverflowException ex ){}
catch( ArgumentNullException ex ){}

... với một số biến thể điên rồ của mùi mã tiếp theo này, ý tôi là, chỉ để giả vờ rằng bạn đang lưu một vài tổ hợp phím.

// sorta sucks, let's be honest...
try
{
    // try some stuff
}
catch( Exception ex )
{
    if (ex is FormatException ||
        ex is OverflowException ||
        ex is ArgumentNullException)
    {
        // write to a log, whatever...
        return;
    }
    throw;
}

Bởi vì nó chắc chắn không tự động dễ đọc hơn.

Cấp, tôi để lại ba trường hợp giống hệt nhau trong /* write to a log, whatever... */ return;ví dụ đầu tiên.

Nhưng đó là loại quan điểm của tôi. Bạn đã nghe nói về các chức năng / phương thức, phải không? Nghiêm túc. Viết một ErrorHandlerhàm chung và, giống như, gọi nó từ mỗi khối bắt.

Nếu bạn hỏi tôi, ví dụ thứ hai (với các từ khóa ifiscả hai đều dễ đọc hơn đáng kể và đồng thời dễ bị lỗi hơn đáng kể trong giai đoạn bảo trì dự án của bạn.

Giai đoạn bảo trì, đối với bất kỳ ai có thể tương đối mới với lập trình, sẽ chiếm 98,7% hoặc hơn trong toàn bộ thời gian thực hiện dự án của bạn, và người nghèo khó thực hiện bảo trì gần như chắc chắn sẽ là một người khác ngoài bạn. Và có một cơ hội rất tốt họ sẽ dành 50% thời gian cho công việc chửi rủa tên bạn.

Và tất nhiên FxCop vỏ vào bạn và do đó bạn phải cũng thêm một thuộc tính để mã của bạn đã chính xác nén để làm với các chương trình đang chạy, và chỉ có nói với FxCop để bỏ qua một vấn đề mà trong 99,9% các trường hợp nó là hoàn toàn đúng trong việc gắn cờ. Và xin lỗi, tôi có thể nhầm, nhưng không phải thuộc tính "bỏ qua" đó thực sự được biên dịch vào ứng dụng của bạn sao?

Sẽ đặt toàn bộ ifbài kiểm tra trên một dòng làm cho nó dễ đọc hơn? Tôi không nghĩ vậy. Ý tôi là, tôi đã có một lập trình viên khác tranh cãi kịch liệt một thời gian dài trước đây rằng việc đặt nhiều mã trên một dòng sẽ làm cho nó "chạy nhanh hơn". Nhưng tất nhiên anh ấy đã ăn hạt dẻ. Cố gắng giải thích cho anh ta (với khuôn mặt thẳng thắn - đầy thách thức) làm thế nào trình thông dịch hoặc trình biên dịch sẽ chia dòng dài đó thành các câu lệnh một dòng riêng biệt - về cơ bản giống với kết quả nếu anh ta đi trước và chỉ làm cho mã có thể đọc được thay vì cố gắng thông minh trình biên dịch - không có tác dụng gì với anh ta. Nhưng tôi lạc đề.

Điều này có thể đọc được ít hơn bao nhiêu khi bạn thêm ba loại ngoại lệ, một hoặc hai tháng kể từ bây giờ? (Trả lời: nó ít được đọc hơn nhiều ).

Một trong những điểm chính, thực sự, là hầu hết các điểm định dạng mã nguồn văn bản mà tất cả chúng ta đang xem mỗi ngày là làm cho nó thực sự, thực sự rõ ràng đối với những người khác những gì thực sự xảy ra khi mã chạy. Bởi vì trình biên dịch biến mã nguồn thành một thứ hoàn toàn khác và không quan tâm nhiều đến phong cách định dạng mã của bạn. Vì vậy, tất cả trên một dòng hoàn toàn hút, quá.

Chỉ cần nói ...

// super sucks...
catch( Exception ex )
{
    if ( ex is FormatException || ex is OverflowException || ex is ArgumentNullException )
    {
        // write to a log, whatever...
        return;
    }
    throw;
}

36
Khi tôi lần đầu tiên vấp phải câu hỏi này, tôi đã hết câu trả lời. Thật tuyệt, tôi chỉ có thể bắt tất cả các Exceptions và kiểm tra loại. Tôi nghĩ rằng nó đã làm sạch mã, nhưng một cái gì đó giữ tôi trở lại câu hỏi và tôi thực sự đọc các câu trả lời khác cho câu hỏi. Tôi đã nhai nó một lúc, nhưng tôi phải đồng ý với bạn. Việc sử dụng một chức năng để làm khô mã của bạn sẽ dễ đọc hơn và có thể duy trì hơn là kiểm tra mọi thứ, kiểm tra loại so sánh với danh sách, mã gói và ném. Cảm ơn bạn đã đến muộn và cung cấp tùy chọn thay thế và lành mạnh (IMO). +1.
lỗi

8
Sử dụng chức năng xử lý lỗi sẽ không hoạt động nếu bạn muốn bao gồm a throw;. Bạn sẽ phải lặp lại dòng mã đó trong mỗi khối bắt (rõ ràng không phải là ngày tận thế nhưng đáng nói vì đây là mã cần được lặp lại).
kad81

5
@ kad81, điều đó đúng, nhưng bạn vẫn sẽ nhận được lợi ích của việc viết mã đăng nhập và dọn dẹp ở một nơi và thay đổi nó ở một nơi nếu cần, không có ngữ nghĩa ngớ ngẩn của việc bắt loại Ngoại lệ cơ bản sau đó phân nhánh dựa trên loại ngoại lệ. Và một throw();tuyên bố bổ sung trong mỗi khối khai thác là một mức giá nhỏ phải trả, IMO và vẫn để bạn ở vị trí cần thực hiện thêm việc dọn dẹp loại ngoại lệ cụ thể nếu cần.
Craig

2
Xin chào @Reitffunk, chỉ cần sử dụng Func<Exception, MyEnumType>thay vì Action<Exception>. Đó là Func<T, Result>, với Resultkiểu trả về.
Craig

3
Tôi hoàn toàn đồng ý ở đây. Tôi cũng đọc câu trả lời đầu tiên và suy nghĩ có vẻ hợp lý. Đã chuyển sang 1 chung cho tất cả xử lý ngoại lệ. Một cái gì đó bên trong tôi làm tôi bối rối ... vì vậy tôi đã hoàn nguyên mã. Rồi tình cờ thấy vẻ đẹp này! Đây cần phải là câu trả lời được chấp nhận
Conor Gallagher

372

Như những người khác đã chỉ ra, bạn có thể có một iftuyên bố trong khối bắt của bạn để xác định điều gì đang xảy ra. C # 6 hỗ trợ Bộ lọc ngoại lệ, do đó, những điều sau đây sẽ hoạt động:

try {  }
catch (Exception e) when (MyFilter(e))
{
    
}

Các MyFilterphương pháp sau đó có thể trông giống như thế này:

private bool MyFilter(Exception e)
{
  return e is ArgumentNullException || e is FormatException;
}

Ngoài ra, điều này có thể được thực hiện tất cả nội tuyến (phía bên phải của câu lệnh khi chỉ phải là một biểu thức boolean).

try {  }
catch (Exception e) when (e is ArgumentNullException || e is FormatException)
{
    
}

Điều này khác với việc sử dụng một ifcâu lệnh từ trong catchkhối, sử dụng các bộ lọc ngoại lệ sẽ không làm mất ngăn xếp.

Bạn có thể tải xuống Visual Studio 2015 để kiểm tra điều này.

Nếu bạn muốn tiếp tục sử dụng Visual Studio 2013, bạn có thể cài đặt gói nuget sau:

Cài đặt-Gói Microsoft.Net.Compilers

Tại thời điểm viết, điều này sẽ bao gồm hỗ trợ cho C # 6.

Tham chiếu gói này sẽ khiến dự án được xây dựng bằng phiên bản cụ thể của trình biên dịch C # và Visual Basic có trong gói, trái ngược với bất kỳ phiên bản nào được cài đặt hệ thống.


3
Kiên nhẫn chờ đợi bản phát hành chính thức của 6 ... Tôi muốn thấy điều này sẽ kiểm tra khi điều đó xảy ra.
RubberDuck

@RubberDuck Tôi sắp chết cho toán tử lan truyền null từ C # 6. Cố gắng thuyết phục phần còn lại của nhóm tôi rằng nguy cơ của một trình biên dịch / ngôn ngữ không ổn định là đáng giá. Rất nhiều cải tiến nhỏ với tác động rất lớn. Đối với việc được đánh dấu là câu trả lời, không quan trọng, miễn là mọi người nhận ra điều này sẽ / có thể, tôi rất vui.
Joe

Đúng?! Tôi sẽ có một cái nhìn tốt về cơ sở mã của tôi trong tương lai gần. =) Tôi biết séc không quan trọng, nhưng đưa ra câu trả lời được chấp nhận sẽ sớm bị lỗi thời, tôi hy vọng OP quay lại để kiểm tra điều này để cung cấp cho nó khả năng hiển thị phù hợp.
RubberDuck

Đó là một phần lý do tại sao tôi chưa trao giải @Joe. Tôi muốn điều này được hiển thị. Bạn có thể muốn thêm một ví dụ về bộ lọc nội tuyến cho rõ ràng.
RubberDuck

188

Thật không may trong C #, vì bạn cần một bộ lọc ngoại lệ để làm điều đó và C # không phơi bày tính năng đó của MSIL. VB.NET có khả năng này, vd

Catch ex As Exception When TypeOf ex Is FormatException OrElse TypeOf ex Is OverflowException

Những gì bạn có thể làm là sử dụng một hàm ẩn danh để đóng gói mã lỗi của mình và sau đó gọi nó trong các khối bắt cụ thể đó:

Action onError = () => WebId = Guid.Empty;
try
{
    // something
}
catch (FormatException)
{
    onError();
}
catch (OverflowException)
{
    onError();
}

26
Ý tưởng thú vị và một ví dụ khác là VB.net đôi khi có một số lợi thế thú vị so với C #
Michael Stum

47
@MichaelStum với rằng loại cú pháp tôi khó có thể gọi nó thú vị ở tất cả ... rùng mình
MarioDS

17
Bộ lọc ngoại lệ đang đến trong c # 6! Lưu ý sự khác biệt của việc sử dụng các bộ lọc có lợi cho việc cải tổ lại roslyn.codeplex.com/discussions/541301
Arne Deruwe

@ArneDeruwe Cảm ơn bạn đã liên kết! Tôi vừa học được một lý do quan trọng nữa để không ném lại: throw e;phá hủy stacktrace callstack, throw;phá hủy callstack "only" (khiến cho crash-dumps vô dụng!) Một lý do rất tốt để sử dụng nếu không thể tránh được!
AnorZaken

1
Kể từ bộ lọc ngoại lệ C # 6 có sẵn! Cuối cùng.
Daniel

134

Để hoàn thiện, vì .NET 4.0 , mã có thể được viết lại thành:

Guid.TryParse(queryString["web"], out WebId);

TryPude không bao giờ ném ngoại lệ và trả về false nếu định dạng sai, đặt WebId thành Guid.Empty.


C # 7, bạn có thể tránh giới thiệu một biến trên một dòng riêng biệt:

Guid.TryParse(queryString["web"], out Guid webId);

Bạn cũng có thể tạo các phương thức để phân tích cú pháp trả về các bộ dữ liệu không có trong .NET Framework kể từ phiên bản 4.6:

(bool success, Guid result) TryParseGuid(string input) =>
    (Guid.TryParse(input, out Guid result), result);

Và sử dụng chúng như thế này:

WebId = TryParseGuid(queryString["web"]).result;
// or
var tuple = TryParseGuid(queryString["web"]);
WebId = tuple.success ? tuple.result : DefaultWebId;

Cập nhật vô ích tiếp theo cho câu trả lời vô dụng này xuất hiện khi việc giải cấu trúc các tham số ngoài được thực hiện trong C # 12. :)


19
Chính xác - ngắn gọn và bạn hoàn toàn bỏ qua hình phạt về hiệu suất xử lý ngoại lệ, hình thức xấu của việc cố ý sử dụng ngoại lệ để kiểm soát luồng chương trình và trọng tâm mềm của việc logic chuyển đổi của bạn lan rộng ra, một chút ở đây và một chút ở đó .
Craig

9
Tôi biết những gì bạn có ý nghĩa, nhưng tất nhiên Guid.TryParsekhông bao giờ trở lại Guid.Empty. Nếu chuỗi ở định dạng không chính xác, nó đặt resulttham số đầu ra thành Guid.Empty, nhưng nó trả về false . Tôi đang đề cập đến nó bởi vì tôi đã thấy mã thực hiện mọi thứ theo kiểu Guid.TryParse(s, out guid); if (guid == Guid.Empty) { /* handle invalid s */ }, thường là sai nếu scó thể là đại diện chuỗi Guid.Empty.

14
wow bạn đã trả lời câu hỏi, ngoại trừ việc nó không nằm trong tinh thần của câu hỏi. Vấn đề lớn hơn là một cái gì đó khác :(
nawfal

6
Tất nhiên, mẫu thích hợp để sử dụng TryPude giống như vậy if( Guid.TryParse(s, out guid){ /* success! */ } else { /* handle invalid s */ }, không để lại sự mơ hồ như ví dụ bị hỏng trong đó giá trị đầu vào thực sự có thể là biểu diễn chuỗi của Hướng dẫn.
Craig

2
Câu trả lời này thực sự có thể đúng đối với Guid.Pude, nhưng nó đã bỏ lỡ toàn bộ điểm của câu hỏi ban đầu. Không liên quan gì đến Guid.Pude, nhưng liên quan đến việc bắt Exception vs FormatException / OverflowException / vv.
Conor Gallagher

115

Bộ lọc ngoại lệ hiện có sẵn trong c # 6+. Bạn có thể làm

try
{
       WebId = new Guid(queryString["web"]);
}
catch (Exception ex) when(ex is FormatException || ex is OverflowException)
{
     WebId = Guid.Empty;
}

Trong C # 7.0+, bạn cũng có thể kết hợp điều này với khớp mẫu

try
{
   await Task.WaitAll(tasks);
}
catch (Exception ex) when( ex is AggregateException ae &&
                           ae.InnerExceptions.Count > tasks.Count/2)
{
   //More than half of the tasks failed maybe..? 
}

Phương pháp này được ưa thích không chỉ vì nó đơn giản và rõ ràng, mà còn không phải thư giãn ngăn xếp nếu các điều kiện không được đáp ứng, cung cấp hiệu suất và thông tin chẩn đoán tốt hơn so với suy nghĩ lại.
joe

74

Nếu bạn có thể nâng cấp ứng dụng của mình lên C # 6, bạn thật may mắn. Phiên bản C # mới đã triển khai các bộ lọc Ngoại lệ. Vì vậy, bạn có thể viết này:

catch (Exception ex) when (ex is FormatException || ex is OverflowException) {
    WebId = Guid.Empty;
}

Một số người nghĩ rằng mã này giống như

catch (Exception ex) {                
    if (ex is FormatException || ex is OverflowException) {
        WebId = Guid.Empty;
    }
    throw;
}

Nhưng nó không phải là. Trên thực tế đây là tính năng mới duy nhất trong C # 6 không thể mô phỏng trong các phiên bản trước. Đầu tiên, ném lại có nghĩa là nhiều chi phí hơn là bỏ qua việc bắt. Thứ hai, nó không tương đương về mặt ngữ nghĩa. Tính năng mới bảo vệ ngăn xếp nguyên vẹn khi bạn gỡ lỗi mã của mình. Không có tính năng này, kết xuất sự cố sẽ ít hữu ích hơn hoặc thậm chí vô dụng.

Xem một cuộc thảo luận về điều này trên CodePlex . Và một ví dụ cho thấy sự khác biệt .


4
Ném mà không có ngoại lệ bảo tồn ngăn xếp, nhưng "throw ex" sẽ ghi đè lên nó.
Ivan

32

Nếu bạn không muốn sử dụng một iftuyên bố trong catchphạm vi, trong C# 6.0bạn có thể sử dụng Exception Filterscú pháp mà đã được hỗ trợ bởi CLR trong các phiên bản xem trước nhưng chỉ tồn tại trong VB.NET/ MSIL:

try
{
    WebId = new Guid(queryString["web"]);
}
catch (Exception exception) when (exception is FormatException || ex is OverflowException)
{
    WebId = Guid.Empty;
}

Mã này sẽ chỉ bắt Exceptionkhi nó là một InvalidDataExceptionhoặc ArgumentNullException.

Trên thực tế, bạn có thể đặt bất kỳ điều kiện nào bên trong whenmệnh đề đó :

static int a = 8;

...

catch (Exception exception) when (exception is InvalidDataException && a == 8)
{
    Console.WriteLine("Catch");
}

Lưu ý rằng trái ngược với một iftuyên bố trong catchphạm vi của phạm vi, Exception Filterskhông thể ném Exceptionsvà khi chúng thực hiện hoặc khi điều kiện không xảy ra true, catchđiều kiện tiếp theo sẽ được đánh giá thay thế:

static int a = 7;

static int b = 0;

...

try
{
    throw new InvalidDataException();
}
catch (Exception exception) when (exception is InvalidDataException && a / b == 2)
{
    Console.WriteLine("Catch");
}
catch (Exception exception) when (exception is InvalidDataException || exception is ArgumentException)
{
    Console.WriteLine("General catch");
}

Đầu ra: Bắt chung.

Khi có nhiều thì một true Exception Filter- cái đầu tiên sẽ được chấp nhận:

static int a = 8;

static int b = 4;

...

try
{
    throw new InvalidDataException();
}
catch (Exception exception) when (exception is InvalidDataException && a / b == 2)
{
    Console.WriteLine("Catch");
}
catch (Exception exception) when (exception is InvalidDataException || exception is ArgumentException)
{
    Console.WriteLine("General catch");
}

Đầu ra: Bắt.

Và như bạn có thể thấy trong MSILmã không được dịch thành các ifcâu lệnh, nhưng Filters, và Exceptionskhông thể được ném từ bên trong các khu vực được đánh dấu Filter 1và thay vào đó, Filter 2bộ lọc ném di Exceptionchúc sẽ thất bại, cũng là giá trị so sánh cuối cùng được đẩy vào ngăn xếp trước endfilterlệnh sẽ xác định thành công / thất bại của bộ lọc ( Catch 1 XOR Catch 2 sẽ thực thi tương ứng):

Bộ lọc ngoại lệ MSIL

Ngoài ra, đặc biệt GuidGuid.TryParsephương pháp.


+1 để hiển thị nhiều khi bộ lọc và cung cấp giải thích về những gì xảy ra khi nhiều bộ lọc được sử dụng.
steven87vt

26

Với C # 7 , câu trả lời từ Michael Stum có thể được cải thiện trong khi vẫn giữ được tính dễ đọc của câu lệnh chuyển đổi:

catch (Exception ex)
{
    switch (ex)
    {
        case FormatException _:
        case OverflowException _:
            WebId = Guid.Empty;
            break;
        default:
            throw;
    }
}

Và với C # 8 là biểu thức chuyển đổi:

catch (Exception ex)
{
    WebId = ex switch
    {
        _ when ex is FormatException || ex is OverflowException => Guid.Empty,
        _ => throw ex
    };
}

3
Đây phải là câu trả lời được chấp nhận kể từ IMHO 2018.
MemphiZ

6
Câu trả lời của Mat J sử dụng whenthanh lịch / phù hợp hơn nhiều so với một công tắc.
rgoliveira

@rgoliveira: Tôi đồng ý rằng đối với trường hợp được hỏi trong câu hỏi, câu trả lời của Mat J là thanh lịch và phù hợp hơn. Tuy nhiên, thật khó để đọc nếu bạn có mã khác mà bạn muốn thực thi tùy thuộc vào loại ngoại lệ hoặc nếu bạn thực sự muốn sử dụng trường hợp ngoại lệ. Tất cả các kịch bản có thể được đối xử bình đẳng với tuyên bố chuyển đổi này.
Fabian

1
@Fabian "nếu bạn có mã khác mà bạn muốn thực thi tùy thuộc vào loại ngoại lệ hoặc nếu bạn thực sự muốn sử dụng thể hiện của ngoại lệ", thì bạn chỉ cần tạo một catchkhối khác hoặc bạn cần phải sử dụng mã đó .. Theo kinh nghiệm của tôi, một throw;trong catchkhối của bạn có thể là một mùi mã.
rgoliveira

@rgoliveira: Sử dụng một cú ném trong khối bắt là OK trong một số trường hợp xem liên kết . Vì câu lệnh tình huống thực sự sử dụng liên kết khớp mẫu, bạn không cần truyền nếu bạn thay thế liên kết toán tử loại bỏ (dấu gạch dưới) bằng một tên biến. Đừng hiểu sai ý tôi, tôi đồng ý với bạn rằng các bộ lọc ngoại lệ là cách làm sạch hơn, nhưng nhiều khối bắt thêm nhiều dấu ngoặc nhọn.
Fabian

20

Câu trả lời được chấp nhận có vẻ chấp nhận được, ngoại trừ CodeAnalysis / FxCop sẽ phàn nàn về thực tế rằng nó bắt một loại ngoại lệ chung.

Ngoài ra, có vẻ như toán tử "là" có thể làm giảm hiệu suất một chút.

CA1800: Đừng bỏ qua một cách không cần thiết để "xem xét việc kiểm tra kết quả của toán tử 'as'", nhưng nếu bạn làm điều đó, bạn sẽ viết nhiều mã hơn nếu bạn nắm bắt từng ngoại lệ riêng biệt.

Dù sao, đây là những gì tôi sẽ làm:

bool exThrown = false;

try
{
    // Something
}
catch (FormatException) {
    exThrown = true;
}
catch (OverflowException) {
    exThrown = true;
}

if (exThrown)
{
    // Something else
}

19
Nhưng hãy lưu ý rằng bạn không thể lấy lại ngoại lệ mà không mất dấu vết ngăn xếp nếu bạn làm như thế này. (Xem bình luận của Michael Stum cho câu trả lời được chấp nhận)
René

2
Có thể cải thiện mẫu này bằng cách lưu trữ ngoại lệ (vui lòng loại trừ định dạng kém - Tôi không thể tìm ra cách đưa mã vào nhận xét): Exception ex = null; thử {// một cái gì đó} bắt (FormatException e) {ex = e; } Catch (OverflowException e) {ex = e; } if (ex! = null) {// một cái gì đó khác và đối phó với ex}
Jesse Weigert

3
@Jlieweigert: 1. Bạn có thể sử dụng backticks để cung cấp cho một đoạn văn bản một phông chữ đơn sắc và nền màu xám nhạt. 2. Bạn vẫn không thể lấy lại ngoại lệ ban đầu, bao gồm cả stacktrace .
Oliver

2
@CleverNeologism mặc dù có thể đúng là việc sử dụng istoán tử có thể có tác động tiêu cực nhẹ đến hiệu suất, nhưng cũng đúng là một trình xử lý ngoại lệ không phải là nơi quá quan tâm đến việc tối ưu hóa hiệu suất. Nếu ứng dụng của bạn dành quá nhiều thời gian cho các trình xử lý ngoại lệ để tối ưu hóa hiệu suất sẽ tạo ra sự khác biệt thực sự trong hiệu suất ứng dụng, thì sẽ có những vấn đề về mã khác cần xem xét kỹ. Có nói rằng, tôi vẫn không thích giải pháp này vì bạn mất dấu vết ngăn xếp và vì việc dọn dẹp được xóa theo ngữ cảnh khỏi câu lệnh bắt.
Craig

3
Lần duy nhất istoán tử làm giảm hiệu suất là nếu sau đó bạn thực hiện một asthao tác (do đó họ đủ điều kiện quy tắc một cách không cần thiết ). Nếu tất cả những gì bạn đang làm là kiểm tra dàn diễn viên mà không thực sự cần thực hiện dàn diễn viên, thì istoán tử chính xác là những gì bạn muốn sử dụng.
sốt

19

trong C # 6, cách tiếp cận được đề xuất là sử dụng Bộ lọc ngoại lệ, đây là một ví dụ:

 try
 {
      throw new OverflowException();
 }
 catch(Exception e ) when ((e is DivideByZeroException) || (e is OverflowException))
 {
       // this will execute iff e is DividedByZeroEx or OverflowEx
       Console.WriteLine("E");
 }

18

Đây là một biến thể của câu trả lời của Matt (tôi cảm thấy rằng điều này sạch hơn một chút) ... sử dụng một phương pháp:

public void TryCatch(...)
{
    try
    {
       // something
       return;
    }
    catch (FormatException) {}
    catch (OverflowException) {}

    WebId = Guid.Empty;
}

Bất kỳ trường hợp ngoại lệ nào khác sẽ được đưa ra và mã WebId = Guid.Empty;sẽ không bị tấn công. Nếu bạn không muốn các ngoại lệ khác làm sập chương trình của mình, chỉ cần thêm SAU hai lần bắt khác:

...
catch (Exception)
{
     // something, if anything
     return; // only need this if you follow the example I gave and put it all in a method
}

-1 Điều này sẽ thực thi WebId = Guid.Emtpytrong trường hợp không có ngoại lệ được ném.
Sepster

4
@sepster Tôi nghĩ câu lệnh return sau "// Something" được ngụ ý ở đây. Tôi không thực sự thích giải pháp, nhưng đây là một biến thể mang tính xây dựng trong cuộc thảo luận. +1 để hoàn tác downvote của bạn :-)
toong

@Sepster toong là đúng, tôi giả sử rằng nếu bạn muốn quay lại đó, thì bạn sẽ đặt một ... Tôi đã cố gắng làm cho câu trả lời của mình đủ chung để áp dụng cho tất cả các tình huống trong trường hợp những người khác có câu hỏi tương tự nhưng không chính xác sẽ có lợi như tốt. Tuy nhiên, để có biện pháp tốt, tôi đã thêm một returncâu trả lời của mình. Cảm ơn các đầu vào.
bsara

18

Câu trả lời của Joseph Daigle là một giải pháp tốt, nhưng tôi thấy cấu trúc sau đây gọn gàng hơn và ít bị lỗi hơn.

catch(Exception ex)
{   
    if (!(ex is SomeException || ex is OtherException)) throw;

    // Handle exception
}

Có một vài lợi thế của việc đảo ngược biểu thức:

  • Một tuyên bố trở lại là không cần thiết
  • Mã không được lồng
  • Không có nguy cơ quên các câu lệnh 'ném' hoặc 'trả lại' mà trong giải pháp của Joseph được tách ra khỏi biểu thức.

Nó thậm chí có thể được nén thành một dòng duy nhất (mặc dù không đẹp lắm)

catch(Exception ex) { if (!(ex is SomeException || ex is OtherException)) throw;

    // Handle exception
}

Chỉnh sửa: Các bộ lọc ngoại lệ trong C # 6.0 sẽ thực hiện cú pháp một chút bụi và đi kèm với một số lợi ích khác đối với bất kỳ giải pháp hiện tại. (đáng chú ý nhất là để ngăn xếp không hề hấn gì)

Đây là cách giải quyết vấn đề tương tự bằng cú pháp C # 6.0:

catch(Exception ex) when (ex is SomeException || ex is OtherException)
{
    // Handle exception
}

2
+1, đây là câu trả lời tốt nhất. Nó tốt hơn câu trả lời hàng đầu chủ yếu là vì không có return, mặc dù đảo ngược điều kiện cũng tốt hơn một chút.
DCShannon

Tôi thậm chí không nghĩ về điều đó. Bắt tốt, tôi sẽ thêm nó vào danh sách.
Stefan T

16

@Micheal

Phiên bản sửa đổi một chút của mã của bạn:

catch (Exception ex)
{
   Type exType = ex.GetType();
   if (exType == typeof(System.FormatException) || 
       exType == typeof(System.OverflowException)
   {
       WebId = Guid.Empty;
   } else {
      throw;
   }
}

So sánh chuỗi là xấu xí và chậm.


21
Tại sao không sử dụng từ khóa "is"?
Chris Pietschmann

29
@Michael - Nếu Microsoft giới thiệu, giả sử, StringTooLongException có nguồn gốc từ FormatException thì đó vẫn là một ngoại lệ định dạng, chỉ là một trường hợp cụ thể. Nó phụ thuộc vào việc bạn muốn ngữ nghĩa của 'bắt loại ngoại lệ chính xác này' hay 'bắt ngoại lệ có nghĩa là định dạng của chuỗi sai'.
Greg Beech

6
@Michael - Ngoài ra, lưu ý rằng "Catch (FormatException ex) có ngữ nghĩa sau, nó sẽ bắt bất cứ thứ gì có nguồn gốc từ FormatException.
Greg Beech

14
@Alex số "ném" mà không có "ex" mang ngoại lệ ban đầu, bao gồm cả dấu vết ngăn xếp ban đầu, lên. Thêm "ex" làm cho thiết lập lại dấu vết ngăn xếp, do đó bạn thực sự có một ngoại lệ khác với bản gốc. Tôi chắc rằng người khác có thể giải thích nó tốt hơn tôi. :)
Samantha Branham

13
-1: Mã này là vô cùng mong manh - một nhà phát triển thư viện có thể mong đợi để thay thế throw new FormatException();với throw new NewlyDerivedFromFormatException();mà không vi phạm mã sử dụng thư viện, và nó sẽ giữ đúng cho tất cả các trường hợp xử lý trừ trường hợp một người nào đó sử dụng ngoại lệ ==thay vì is(hoặc đơn giản catch (FormatException)).
Sam Harwell

13

Làm thế nào về

try
{
    WebId = Guid.Empty;
    WebId = new Guid(queryString["web"]);
}
catch (FormatException)
{
}
catch (OverflowException)
{
}

Điều đó chỉ hoạt động nếu Mã bắt có thể được chuyển hoàn toàn vào Khối thử. Nhưng mã hình ảnh nơi bạn thực hiện nhiều thao tác cho một đối tượng và một thao tác ở giữa không thành công và bạn muốn "đặt lại" đối tượng.
Michael Stum

4
Trong trường hợp đó tôi sẽ thêm một chức năng thiết lập lại và gọi nó từ nhiều khối bắt.
Maurice

12
catch (Exception ex)
{
    if (!(
        ex is FormatException ||
        ex is OverflowException))
    {
        throw;
    }
    Console.WriteLine("Hello");
}

11

Thận trọng và cảnh báo: Một loại khác, phong cách chức năng.

Những gì trong liên kết không trả lời trực tiếp câu hỏi của bạn, nhưng thật tầm thường khi mở rộng nó thành:

static void Main() 
{ 
    Action body = () => { ...your code... };

    body.Catch<InvalidOperationException>() 
        .Catch<BadCodeException>() 
        .Catch<AnotherException>(ex => { ...handler... })(); 
}

(Về cơ bản cung cấp một Catchquá tải trống khác mà trả về chính nó)

Câu hỏi lớn hơn cho vấn đề này là tại sao . Tôi không nghĩ rằng chi phí vượt xa mức tăng ở đây :)


1
Một lợi thế có thể có của phương pháp này là có một sự khác biệt về ngữ nghĩa giữa việc bắt và lấy lại một ngoại lệ so với việc không nắm bắt nó; trong một số trường hợp, mã phải hành động theo một ngoại lệ mà không bắt được nó. Một điều như vậy là có thể có trong vb.net, nhưng không phải trong C # trừ khi người ta sử dụng một trình bao bọc được viết bằng vb.net và được gọi từ C #.
supercat

1
Làm thế nào để hành động trên một ngoại lệ mà không bắt nó? Tôi không hoàn toàn hiểu bạn.
nawfal

@nawful ... sử dụng bộ lọc vb - chức năng lọc (ví dụ như ngoại lệ): LogEx (ex): return false ... sau đó trong dòng bắt: bắt ex khi bộ lọc (ví dụ)
FastAl

1
@FastAl Đây có phải là bộ lọc ngoại lệ nào cho phép trong C # 6 không?
HimBromBeere

@HimBromBeere yep chúng tương tự trực tiếp
FastAl

9

Cập nhật 2015-12-15: Xem https://stackoverflow.com/a/22864936/1718702 cho C # 6. Đó là một trình dọn dẹp và bây giờ là tiêu chuẩn trong ngôn ngữ.

Dành cho những người muốn có một giải pháp thanh lịch hơn để bắt một lần và lọc các ngoại lệ, tôi sử dụng một phương pháp mở rộng như được trình bày dưới đây.

Tôi đã có phần mở rộng này trong thư viện của mình, ban đầu được viết cho các mục đích khác, nhưng nó hoạt động hoàn hảo để typekiểm tra các ngoại lệ. Thêm vào đó, imho, nó trông sạch sẽ hơn một loạt các ||tuyên bố. Ngoài ra, không giống như câu trả lời được chấp nhận, tôi thích xử lý ngoại lệ rõ ràng vì vậy ex is ...có hành vi không thể chấp nhận được vì các lớp bị loại bỏ được gán cho các kiểu cha mẹ).

Sử dụng

if (ex.GetType().IsAnyOf(
    typeof(FormatException),
    typeof(ArgumentException)))
{
    // Handle
}
else
    throw;

Tiện ích mở rộng IsAnyOf.cs (Xem ví dụ xử lý lỗi đầy đủ cho các phụ thuộc)

namespace Common.FluentValidation
{
    public static partial class Validate
    {
        /// <summary>
        /// Validates the passed in parameter matches at least one of the passed in comparisons.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_comparisons">Values to compare against.</param>
        /// <returns>True if a match is found.</returns>
        /// <exception cref="ArgumentNullException"></exception>
        public static bool IsAnyOf<T>(this T p_parameter, params T[] p_comparisons)
        {
            // Validate
            p_parameter
                .CannotBeNull("p_parameter");
            p_comparisons
                .CannotBeNullOrEmpty("p_comparisons");

            // Test for any match
            foreach (var item in p_comparisons)
                if (p_parameter.Equals(item))
                    return true;

            // Return no matches found
            return false;
        }
    }
}

Ví dụ xử lý lỗi đầy đủ (Sao chép-Dán vào ứng dụng Bảng điều khiển mới)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Common.FluentValidation;

namespace IsAnyOfExceptionHandlerSample
{
    class Program
    {
        static void Main(string[] args)
        {
            // High Level Error Handler (Log and Crash App)
            try
            {
                Foo();
            }
            catch (OutOfMemoryException ex)
            {
                Console.WriteLine("FATAL ERROR! System Crashing. " + ex.Message);
                Console.ReadKey();
            }
        }

        static void Foo()
        {
            // Init
            List<Action<string>> TestActions = new List<Action<string>>()
            {
                (key) => { throw new FormatException(); },
                (key) => { throw new ArgumentException(); },
                (key) => { throw new KeyNotFoundException();},
                (key) => { throw new OutOfMemoryException(); },
            };

            // Run
            foreach (var FooAction in TestActions)
            {
                // Mid-Level Error Handler (Appends Data for Log)
                try
                {
                    // Init
                    var SomeKeyPassedToFoo = "FooParam";

                    // Low-Level Handler (Handle/Log and Keep going)
                    try
                    {
                        FooAction(SomeKeyPassedToFoo);
                    }
                    catch (Exception ex)
                    {
                        if (ex.GetType().IsAnyOf(
                            typeof(FormatException),
                            typeof(ArgumentException)))
                        {
                            // Handle
                            Console.WriteLine("ex was {0}", ex.GetType().Name);
                            Console.ReadKey();
                        }
                        else
                        {
                            // Add some Debug info
                            ex.Data.Add("SomeKeyPassedToFoo", SomeKeyPassedToFoo.ToString());
                            throw;
                        }
                    }
                }
                catch (KeyNotFoundException ex)
                {
                    // Handle differently
                    Console.WriteLine(ex.Message);

                    int Count = 0;
                    if (!Validate.IsAnyNull(ex, ex.Data, ex.Data.Keys))
                        foreach (var Key in ex.Data.Keys)
                            Console.WriteLine(
                                "[{0}][\"{1}\" = {2}]",
                                Count, Key, ex.Data[Key]);

                    Console.ReadKey();
                }
            }
        }
    }
}

namespace Common.FluentValidation
{
    public static partial class Validate
    {
        /// <summary>
        /// Validates the passed in parameter matches at least one of the passed in comparisons.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_comparisons">Values to compare against.</param>
        /// <returns>True if a match is found.</returns>
        /// <exception cref="ArgumentNullException"></exception>
        public static bool IsAnyOf<T>(this T p_parameter, params T[] p_comparisons)
        {
            // Validate
            p_parameter
                .CannotBeNull("p_parameter");
            p_comparisons
                .CannotBeNullOrEmpty("p_comparisons");

            // Test for any match
            foreach (var item in p_comparisons)
                if (p_parameter.Equals(item))
                    return true;

            // Return no matches found
            return false;
        }

        /// <summary>
        /// Validates if any passed in parameter is equal to null.
        /// </summary>
        /// <param name="p_parameters">Parameters to test for Null.</param>
        /// <returns>True if one or more parameters are null.</returns>
        public static bool IsAnyNull(params object[] p_parameters)
        {
            p_parameters
                .CannotBeNullOrEmpty("p_parameters");

            foreach (var item in p_parameters)
                if (item == null)
                    return true;

            return false;
        }
    }
}

namespace Common.FluentValidation
{
    public static partial class Validate
    {
        /// <summary>
        /// Validates the passed in parameter is not null, throwing a detailed exception message if the test fails.
        /// </summary>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_name">Name of tested parameter to assist with debugging.</param>
        /// <exception cref="ArgumentNullException"></exception>
        public static void CannotBeNull(this object p_parameter, string p_name)
        {
            if (p_parameter == null)
                throw
                    new
                        ArgumentNullException(
                        string.Format("Parameter \"{0}\" cannot be null.",
                        p_name), default(Exception));
        }
    }
}

namespace Common.FluentValidation
{
    public static partial class Validate
    {
        /// <summary>
        /// Validates the passed in parameter is not null or an empty collection, throwing a detailed exception message if the test fails.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_name">Name of tested parameter to assist with debugging.</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        public static void CannotBeNullOrEmpty<T>(this ICollection<T> p_parameter, string p_name)
        {
            if (p_parameter == null)
                throw new ArgumentNullException("Collection cannot be null.\r\nParameter_Name: " + p_name, default(Exception));

            if (p_parameter.Count <= 0)
                throw new ArgumentOutOfRangeException("Collection cannot be empty.\r\nParameter_Name: " + p_name, default(Exception));
        }

        /// <summary>
        /// Validates the passed in parameter is not null or empty, throwing a detailed exception message if the test fails.
        /// </summary>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_name">Name of tested parameter to assist with debugging.</param>
        /// <exception cref="ArgumentException"></exception>
        public static void CannotBeNullOrEmpty(this string p_parameter, string p_name)
        {
            if (string.IsNullOrEmpty(p_parameter))
                throw new ArgumentException("String cannot be null or empty.\r\nParameter_Name: " + p_name, default(Exception));
        }
    }
}

Hai bài kiểm tra đơn vị NUnit mẫu

Kết hợp hành vi cho Exceptioncác loại là chính xác (ví dụ: Một đứa trẻ KHÔNG phải là đối sánh cho bất kỳ loại cha mẹ nào của nó).

using System;
using System.Collections.Generic;
using Common.FluentValidation;
using NUnit.Framework;

namespace UnitTests.Common.Fluent_Validations
{
    [TestFixture]
    public class IsAnyOf_Tests
    {
        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void IsAnyOf_ArgumentNullException_ShouldNotMatch_ArgumentException_Test()
        {
            Action TestMethod = () => { throw new ArgumentNullException(); };

            try
            {
                TestMethod();
            }
            catch (Exception ex)
            {
                if (ex.GetType().IsAnyOf(
                    typeof(ArgumentException), /*Note: ArgumentNullException derrived from ArgumentException*/
                    typeof(FormatException),
                    typeof(KeyNotFoundException)))
                {
                    // Handle expected Exceptions
                    return;
                }

                //else throw original
                throw;
            }
        }

        [Test, ExpectedException(typeof(OutOfMemoryException))]
        public void IsAnyOf_OutOfMemoryException_ShouldMatch_OutOfMemoryException_Test()
        {
            Action TestMethod = () => { throw new OutOfMemoryException(); };

            try
            {
                TestMethod();
            }
            catch (Exception ex)
            {
                if (ex.GetType().IsAnyOf(
                    typeof(OutOfMemoryException),
                    typeof(StackOverflowException)))
                    throw;

                /*else... Handle other exception types, typically by logging to file*/
            }
        }
    }
}

1
Tăng cường ngôn ngữ không phải là "thanh lịch hơn". Ở nhiều nơi điều này thực sự tạo ra một địa ngục bảo trì. Nhiều năm sau, nhiều lập trình viên không tự hào về những gì quái vật họ tạo ra. Đó không phải là những gì bạn đã từng đọc. Nó có thể gây ra "hả?" hiệu ứng, hoặc thậm chí "WTFs" nghiêm trọng. Đôi khi thật khó hiểu. Điều duy nhất nó làm là làm cho mã khó nắm bắt hơn nhiều đối với những người cần xử lý nó sau này trong bảo trì - chỉ vì một lập trình viên duy nhất đã cố gắng "thông minh". Qua nhiều năm, tôi đã học được rằng những giải pháp "thông minh" đó hiếm khi cũng là những giải pháp tốt.
Kaii

1
hoặc trong một vài từ: gắn bó với possibilites mà ngôn ngữ vốn cung cấp. đừng cố gắng ghi đè ngữ nghĩa của ngôn ngữ, chỉ vì bạn không thích chúng. Đồng nghiệp của bạn (và có thể cả tương lai - tôi) sẽ cảm ơn bạn, thật lòng.
Kaii

Cũng lưu ý, giải pháp của bạn chỉ xấp xỉ ngữ nghĩa của C # 6 when, giống như bất kỳ phiên bản nào của catch (Exception ex) {if (...) {/*handle*/} throw;}. Giá trị thực sự whenlà bộ lọc chạy trước khi ngoại lệ bị bắt , do đó tránh được tham nhũng / chi phí của việc ném lại. Nó tận dụng tính năng CLR mà trước đây chỉ có thể truy cập vào VB và MSIL.
Marc L.

Thanh lịch hơn? Ví dụ này quá lớn cho một vấn đề đơn giản như vậy và mã trông rất khủng khiếp đến nỗi nó thậm chí không đáng để xem xét. Vui lòng không tạo mã này cho người khác về một dự án thực tế.
KthProg

toàn bộ IsAnyOfphương pháp của bạn có thể được viết lại một cách đơn giảnp_comparisons.Contains(p_parameter)
maksymiuk

7

Vì tôi cảm thấy như những câu trả lời này vừa chạm vào bề mặt, tôi đã cố gắng đào sâu hơn một chút.

Vì vậy, những gì chúng ta thực sự muốn làm là một cái gì đó không biên dịch, nói:

// Won't compile... damn
public static void Main()
{
    try
    {
        throw new ArgumentOutOfRangeException();
    }
    catch (ArgumentOutOfRangeException)
    catch (IndexOutOfRangeException) 
    {
        // ... handle
    }

Lý do chúng tôi muốn điều này là vì chúng tôi không muốn người xử lý ngoại lệ nắm bắt những thứ mà chúng tôi cần sau này trong quy trình. Chắc chắn, chúng ta có thể bắt Ngoại lệ và kiểm tra 'nếu' phải làm gì, nhưng hãy trung thực, chúng ta không thực sự muốn điều đó. (FxCop, vấn đề gỡ lỗi, xấu xí)

Vậy tại sao mã này không được biên dịch - và làm thế nào chúng ta có thể hack nó theo cách mà nó sẽ?

Nếu chúng ta nhìn vào mã, những gì chúng ta thực sự muốn làm là chuyển tiếp cuộc gọi. Tuy nhiên, theo Phân vùng MS II, các khối xử lý ngoại lệ IL sẽ không hoạt động như thế này, trong trường hợp này có ý nghĩa vì điều đó có nghĩa là đối tượng 'ngoại lệ' có thể có các loại khác nhau.

Hoặc để viết nó bằng mã, chúng tôi yêu cầu trình biên dịch làm một cái gì đó như thế này (nó không hoàn toàn chính xác, nhưng đó là điều gần nhất có thể tôi đoán):

// Won't compile... damn
try
{
    throw new ArgumentOutOfRangeException();
}
catch (ArgumentOutOfRangeException e) {
    goto theOtherHandler;
}
catch (IndexOutOfRangeException e) {
theOtherHandler:
    Console.WriteLine("Handle!");
}

Lý do mà điều này sẽ không được biên dịch là khá rõ ràng: đối tượng '$ ngoại lệ' sẽ có loại (giá trị nào được lưu trữ trong các biến 'e')? Cách chúng tôi muốn trình biên dịch xử lý việc này là lưu ý rằng loại cơ sở chung của cả hai ngoại lệ là 'Ngoại lệ', sử dụng biến đó để chứa cả hai ngoại lệ và sau đó chỉ xử lý hai ngoại lệ được bắt. Cách thức này được triển khai trong IL là 'bộ lọc', có sẵn trong VB.Net.

Để làm cho nó hoạt động trong C #, chúng ta cần một biến tạm thời với loại cơ sở 'Ngoại lệ' chính xác. Để kiểm soát luồng mã, chúng ta có thể thêm một số nhánh. Đây là:

    Exception ex;
    try
    {
        throw new ArgumentException(); // for demo purposes; won't be caught.
        goto noCatch;
    }
    catch (ArgumentOutOfRangeException e) {
        ex = e;
    }
    catch (IndexOutOfRangeException e) {
        ex = e;
    }

    Console.WriteLine("Handle the exception 'ex' here :-)");
    // throw ex ?

noCatch:
    Console.WriteLine("We're done with the exception handling.");

Nhược điểm rõ ràng cho điều này là chúng ta không thể ném lại đúng cách, và - hãy trung thực - rằng đó là giải pháp khá xấu xí. Sự xấu xí có thể được khắc phục một chút bằng cách thực hiện loại bỏ nhánh, điều này làm cho giải pháp tốt hơn một chút:

Exception ex = null;
try
{
    throw new ArgumentException();
}
catch (ArgumentOutOfRangeException e)
{
    ex = e;
}
catch (IndexOutOfRangeException e)
{
    ex = e;
}
if (ex != null)
{
    Console.WriteLine("Handle the exception here :-)");
}

Điều đó chỉ để lại 'ném lại'. Để làm việc này, chúng ta cần có khả năng thực hiện việc xử lý bên trong khối 'bắt' - và cách duy nhất để thực hiện công việc này là bằng một đối tượng bắt 'Ngoại lệ'.

Tại thời điểm này, chúng ta có thể thêm một chức năng riêng biệt xử lý các loại Ngoại lệ khác nhau bằng cách sử dụng độ phân giải quá tải hoặc để xử lý Ngoại lệ. Cả hai đều có nhược điểm. Để bắt đầu, đây là cách thực hiện với chức năng trợ giúp:

private static bool Handle(Exception e)
{
    Console.WriteLine("Handle the exception here :-)");
    return true; // false will re-throw;
}

public static void Main()
{
    try
    {
        throw new OutOfMemoryException();
    }
    catch (ArgumentException e)
    {
        if (!Handle(e)) { throw; }
    }
    catch (IndexOutOfRangeException e)
    {
        if (!Handle(e)) { throw; }
    }

    Console.WriteLine("We're done with the exception handling.");

Và giải pháp khác là bắt đối tượng Exception và xử lý nó cho phù hợp. Bản dịch theo nghĩa đen nhất cho điều này, dựa trên bối cảnh ở trên là đây:

try
{
    throw new ArgumentException();
}
catch (Exception e)
{
    Exception ex = (Exception)(e as ArgumentException) ?? (e as IndexOutOfRangeException);
    if (ex != null)
    {
        Console.WriteLine("Handle the exception here :-)");
        // throw ?
    }
    else 
    {
        throw;
    }
}

Vì vậy, để kết luận:

  • Nếu chúng tôi không muốn ném lại, chúng tôi có thể xem xét nắm bắt các ngoại lệ phù hợp và lưu trữ chúng tạm thời.
  • Nếu trình xử lý đơn giản và chúng tôi muốn sử dụng lại mã, giải pháp tốt nhất có lẽ là giới thiệu chức năng trợ giúp.
  • Nếu chúng tôi muốn ném lại, chúng tôi không có lựa chọn nào khác ngoài việc đặt mã vào trình xử lý bắt 'Ngoại lệ', điều này sẽ phá vỡ FxCop và ngoại lệ của trình gỡ lỗi của bạn.

7

Đây là một vấn đề kinh điển mà mọi nhà phát triển C # phải đối mặt cuối cùng.

Hãy để tôi chia câu hỏi của bạn thành 2 câu hỏi. Đầu tiên,

Tôi có thể bắt được nhiều ngoại lệ cùng một lúc không?

Tóm lại, không.

Điều này dẫn đến câu hỏi tiếp theo,

Làm cách nào để tránh viết mã trùng lặp do tôi không thể bắt được nhiều loại ngoại lệ trong cùng một khối Catch ()?

Với mẫu cụ thể của bạn, trong đó giá trị dự phòng rẻ để xây dựng, tôi muốn làm theo các bước sau:

  1. Khởi tạo WebId thành giá trị dự phòng.
  2. Xây dựng một hướng dẫn mới trong một biến tạm thời.
  3. Đặt WebId cho biến tạm thời được xây dựng đầy đủ. Đặt đây là tuyên bố cuối cùng của khối thử {}.

Vì vậy, mã trông như:

try
{
    WebId = Guid.Empty;
    Guid newGuid = new Guid(queryString["web"]);
    // More initialization code goes here like 
    // newGuid.x = y;
    WebId = newGuid;
}
catch (FormatException) {}
catch (OverflowException) {}

Nếu có bất kỳ ngoại lệ nào được đưa ra, thì WebId sẽ không bao giờ được đặt thành giá trị được xây dựng một nửa và vẫn là Guid.Empty.

Nếu việc xây dựng giá trị dự phòng là tốn kém và việc đặt lại một giá trị rẻ hơn nhiều, thì tôi sẽ chuyển mã đặt lại vào chức năng của chính nó:

try
{
    WebId = new Guid(queryString["web"]);
    // More initialization code goes here.
}
catch (FormatException) {
    Reset(WebId);
}
catch (OverflowException) {
    Reset(WebId);
}

Điều này thật tuyệt, "mã hóa sinh thái" tức là bạn đang suy nghĩ về dấu chân dữ liệu và mã của mình và đảm bảo không rò rỉ một nửa giá trị được xử lý. Rất vui được làm theo mô hình này nhờ Jeffrey!
Tahir Khalid

6

Vì vậy, bạn có thể lặp lại rất nhiều mã trong mỗi chuyển đổi ngoại lệ không? Âm thanh như trích xuất một phương pháp sẽ là ý tưởng của chúa, phải không?

Vì vậy, mã của bạn đi xuống này:

MyClass instance;
try { instance = ... }
catch(Exception1 e) { Reset(instance); }
catch(Exception2 e) { Reset(instance); }
catch(Exception) { throw; }

void Reset(MyClass instance) { /* reset the state of the instance */ }

Tôi tự hỏi tại sao không ai nhận thấy rằng sao chép mã.

Từ C # 6, bạn cũng có các bộ lọc ngoại lệ như đã được đề cập bởi những người khác. Vì vậy, bạn có thể sửa đổi mã ở trên để này:

try { ... }
catch(Exception e) when(e is Exception1 || e is Exception2)
{ 
    Reset(instance); 
}

3
"Tôi tự hỏi tại sao không ai nhận thấy rằng sao chép mã." - ờ, cái gì? Các toàn bộ điểm của câu hỏi là để loại bỏ sự trùng lặp mã.
Đánh dấu Amery

4

Muốn thêm câu trả lời ngắn của tôi cho chủ đề đã dài này. Một cái gì đó chưa được đề cập là thứ tự ưu tiên của các câu lệnh bắt, cụ thể hơn là bạn cần phải biết phạm vi của từng loại ngoại lệ bạn đang cố gắng nắm bắt.

Ví dụ: nếu bạn sử dụng ngoại lệ "bắt tất cả" như Ngoại lệ, nó sẽ đi trước tất cả các câu lệnh bắt khác và rõ ràng bạn sẽ gặp lỗi trình biên dịch, tuy nhiên nếu bạn đảo ngược thứ tự bạn có thể xâu chuỗi các câu lệnh bắt của bạn (tôi nghĩ là một mô hình chống ) bạn có thể đặt loại Ngoại lệ bắt tất cả ở dưới cùng và điều này sẽ nắm bắt bất kỳ trường hợp ngoại lệ nào không phục vụ cao hơn trong khối try..catch của bạn:

            try
            {
                // do some work here
            }
            catch (WebException ex)
            {
                // catch a web excpetion
            }
            catch (ArgumentException ex)
            {
                // do some stuff
            }
            catch (Exception ex)
            {
                // you should really surface your errors but this is for example only
                throw new Exception("An error occurred: " + ex.Message);
            }

Tôi đặc biệt khuyên mọi người nên xem lại tài liệu MSDN này:

Phân cấp ngoại lệ


4

Có thể cố gắng giữ cho mã của bạn đơn giản, chẳng hạn như đặt mã chung vào một phương thức, như bạn sẽ làm trong bất kỳ phần nào khác của mã không nằm trong mệnh đề bắt?

Ví dụ:

try
{
    // ...
}
catch (FormatException)
{
    DoSomething();
}
catch (OverflowException)
{
    DoSomething();
}

// ...

private void DoSomething()
{
    // ...
}

Làm thế nào tôi sẽ làm điều đó, cố gắng tìm đơn giản là mẫu đẹp


3

Lưu ý rằng tôi đã tìm thấy một cách để làm điều đó, nhưng điều này trông giống như tài liệu cho The Daily WTF :

catch (Exception ex)
{
    switch (ex.GetType().Name)
    {
        case "System.FormatException":
        case "System.OverflowException":
            WebId = Guid.Empty;
            break;
        default:
            throw;
    }
}

9
-1 phiếu bầu, +5 WTF :-) Điều này không nên được đánh dấu là một câu trả lời, nhưng nó không phải là vấn đề.
Aaron

1
Không quan trọng bằng cách nào chúng ta có thể làm điều đó. Nhưng anh không ngồi yên và đưa ra quan điểm của mình để giải quyết nó. Thực sự đánh giá cao.
Maxymus

2
Mặc dù vậy, đừng thực sự làm điều này, hãy sử dụng Bộ lọc ngoại lệ trong C # 6 hoặc bất kỳ câu trả lời nào khác - Tôi đặt vấn đề này ở đây cụ thể là "Đây là một cách, nhưng nó rất tệ và tôi muốn làm điều gì đó tốt hơn".
Michael Stum

TẠI SAO điều này là xấu? Tôi đã bối rối khi bạn không thể sử dụng ngoại lệ trong một câu lệnh chuyển đổi trực tiếp.
MKesper

3
@MKesper Tôi thấy một vài lý do nó tệ. Nó yêu cầu viết tên lớp đủ điều kiện dưới dạng chuỗi ký tự, dễ bị lỗi chính tả mà trình biên dịch không thể cứu bạn khỏi. (Điều này rất có ý nghĩa vì trong nhiều cửa hàng, các trường hợp lỗi được kiểm tra ít hơn và do đó, các lỗi nhỏ trong đó có nhiều khả năng bị bỏ qua.) Nó cũng sẽ không khớp với một ngoại lệ là một lớp con của một trong các trường hợp được chỉ định. Và, do là các chuỗi, các trường hợp sẽ bị bỏ qua bởi các công cụ như "Tìm tất cả tài liệu tham khảo" của VS - thích hợp nếu bạn muốn thêm một bước dọn dẹp ở mọi nơi một ngoại lệ cụ thể được bắt gặp.
Đánh dấu Amery

2

Điều đáng nói ở đây. Bạn có thể trả lời nhiều kết hợp (Lỗi ngoại lệ và ngoại lệ.message).

Tôi gặp phải một tình huống sử dụng khi cố gắng truyền đối tượng điều khiển trong một tệp dữ liệu, với nội dung là TextBox, TextBlock hoặc CheckBox. Trong trường hợp này, Ngoại lệ được trả về là như nhau, nhưng thông báo khác nhau.

try
{
 //do something
}
catch (Exception ex) when (ex.Message.Equals("the_error_message1_here"))
{
//do whatever you like
} 
catch (Exception ex) when (ex.Message.Equals("the_error_message2_here"))
{
//do whatever you like
} 

0

Tôi muốn đề xuất câu trả lời ngắn nhất (thêm một kiểu chức năng ):

        Catch<FormatException, OverflowException>(() =>
            {
                WebId = new Guid(queryString["web"]);
            },
            exception =>
            {
                WebId = Guid.Empty;
            });

Để làm điều này, bạn cần tạo một số quá tải phương thức "Catch", tương tự như System.Action:

    [DebuggerNonUserCode]
    public static void Catch<TException1, TException2>(Action tryBlock,
        Action<Exception> catchBlock)
    {
        CatchMany(tryBlock, catchBlock, typeof(TException1), typeof(TException2));
    }

    [DebuggerNonUserCode]
    public static void Catch<TException1, TException2, TException3>(Action tryBlock,
        Action<Exception> catchBlock)
    {
        CatchMany(tryBlock, catchBlock, typeof(TException1), typeof(TException2), typeof(TException3));
    }

và nhiều như bạn muốn. Nhưng bạn cần phải thực hiện một lần và bạn có thể sử dụng nó trong tất cả các dự án của bạn (hoặc, nếu bạn đã tạo gói nuget, chúng tôi cũng có thể sử dụng nó).

Và CatchMany thực hiện:

    [DebuggerNonUserCode]
    public static void CatchMany(Action tryBlock, Action<Exception> catchBlock,
        params Type[] exceptionTypes)
    {
        try
        {
            tryBlock();
        }
        catch (Exception exception)
        {
            if (exceptionTypes.Contains(exception.GetType())) catchBlock(exception);
            else throw;
        }
    }

ps Tôi chưa đặt kiểm tra null cho đơn giản mã, xem xét để thêm xác nhận tham số.

ps2 Nếu bạn muốn trả về một giá trị từ lệnh bắt, thì cần phải thực hiện các phương thức Catch tương tự, nhưng với trả về và Func thay vì Action trong các tham số.


-15

Chỉ cần gọi thử và bắt hai lần.

try
{
    WebId = new Guid(queryString["web"]);
}
catch (FormatException)
{
    WebId = Guid.Empty;
}
try
{
    WebId = new Guid(queryString["web"]);
}
catch (OverflowException)
{
    WebId = Guid.Empty;
}

Nó chỉ đơn giản vậy thôi !!


3
ừm đây là đánh bại mục đích của câu hỏi Ông hỏi câu hỏi này để thoát khỏi mã trùng lặp. Câu trả lời này thêm mã trùng lặp.
James Esh

-23

Trong c # 6.0, Bộ lọc ngoại lệ là các cải tiến để xử lý ngoại lệ

try
{
    DoSomeHttpRequest();
}
catch (System.Web.HttpException e)
{
    switch (e.GetHttpCode())
    {
        case 400:
            WriteLine("Bad Request");
        case 500:
            WriteLine("Internal Server Error");
        default:
            WriteLine("Generic Error");
    }
}

13
Ví dụ này không hiển thị bất kỳ việc sử dụng bộ lọc ngoại lệ.
user247702

Đây là cách tiêu chuẩn để lọc ngoại lệ trong c # 6.0
Kashif

5
Hãy xem lại chính xác các bộ lọc ngoại lệ là gì. Bạn không sử dụng bộ lọc ngoại lệ trong ví dụ của mình. Có một ví dụ thích hợp trong câu trả lời này được đăng một năm trước bạn.
user247702

6
Một ví dụ về lọc ngoại lệ sẽ làcatch (HttpException e) when e.GetHttpCode() == 400 { WriteLine("Bad Request"; }
saluce
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.