Câu hỏi Bắt ngoại lệ với "bắt, khi nào"


Tôi đã xem xét tính năng mới này trong C # cho phép một trình xử lý bắt được thực thi khi một điều kiện cụ thể được đáp ứng.

int i = 0;
try
{
    throw new ArgumentNullException(nameof(i));
}
catch (ArgumentNullException e)
when (i == 1)
{
    Console.WriteLine("Caught Argument Null Exception");
}

Tôi đang cố gắng hiểu khi nào điều này có thể hữu ích.

Một kịch bản có thể là một cái gì đó như thế này:

try
{
    DatabaseUpdate()
}
catch (SQLException e)
when (driver == "MySQL")
{
    //MySQL specific error handling and wrapping up the exception
}
catch (SQLException e)
when (driver == "Oracle")
{
    //Oracle specific error handling and wrapping up of exception
}
..

nhưng đây lại là một cái gì đó mà tôi có thể làm trong cùng một trình xử lý và ủy quyền cho các phương thức khác nhau tùy thuộc vào loại trình điều khiển. Điều này có làm cho mã dễ hiểu hơn không? Có thể cho là không.

Một kịch bản khác mà tôi có thể nghĩ đến là:

try
{
    SomeOperation();
}
catch(SomeException e)
when (Condition == true)
{
    //some specific error handling that this layer can handle
}
catch (Exception e) //catchall
{
    throw;
}

Một lần nữa đây là một cái gì đó mà tôi có thể làm như:

try
{
    SomeOperation();
}
catch(SomeException e)
{
    if (condition == true)
    {
        //some specific error handling that this layer can handle
    }
    else
        throw;
}

Liệu việc sử dụng tính năng 'catch, when' có làm cho việc xử lý ngoại lệ nhanh hơn bởi vì trình xử lý bị bỏ qua và ngăn xếp thư có thể xảy ra sớm hơn khi so sánh với việc xử lý các trường hợp sử dụng cụ thể trong trình xử lý? Có trường hợp sử dụng cụ thể nào phù hợp với tính năng này tốt hơn mà mọi người có thể áp dụng như một thực hành tốt không?


76
2017-07-21 07:31


gốc


Sẽ hữu ích nếu when cần truy cập vào ngoại lệ - Tim Schmelter
Nhưng đó là điều mà chúng ta có thể làm trong chính bộ xử lý cũng đúng. Có bất kỳ lợi ích nào ngoài 'mã được tổ chức hơi hơn' không? - Madhusudhan
Nhưng sau đó bạn đã xử lý ngoại lệ mà bạn không muốn. Nếu bạn muốn bắt nó ở một nơi khác trong try..catch...catch..catch..finally? - Tim Schmelter
@ user3493289: Theo sau đối số đó, chúng tôi không cần kiểm tra kiểu tự động trong các trình xử lý ngoại lệ: Chúng tôi chỉ có thể cho phép catch (Exception ex), kiểm tra loại và throw nếu không thì. Mã tổ chức nhẹ hơn (aka tránh mã tiếng ồn) là chính xác lý do tại sao tính năng này tồn tại. (Điều này thực sự đúng với rất nhiều tính năng.) - Heinzi
@TimSchmelter Cảm ơn. Đăng nó như là một câu trả lời và tôi sẽ chấp nhận nó. Vì vậy, kịch bản thực tế sẽ là 'nếu điều kiện để xử lý tùy thuộc vào ngoại lệ', thì hãy sử dụng tính năng này / - Madhusudhan


Các câu trả lời:


Các khối catch đã cho phép bạn lọc trên kiểu của ngoại lệ:

catch (SomeSpecificExceptionType e) {...}

Các when khoản cho phép bạn mở rộng bộ lọc này thành các biểu thức chung.

Như vậy, bạn sử dụng when khoản cho các trường hợp kiểu ngoại lệ không đủ khác biệt để xác định liệu ngoại lệ có nên được xử lý ở đây hay không.


Trường hợp sử dụng phổ biến là các loại ngoại lệ thực sự là vỏ bánh cho nhiều loại lỗi khác nhau.

Đây là một trường hợp mà tôi đã thực sự sử dụng (trong VB, đã có tính năng này trong một thời gian):

try
{
    SomeLegacyComOperation();
}
catch (COMException e) when (e.ErrorCode == 0x1234)
{
    // Handle the *specific* error I was expecting. 
}

Giống với SqlException, cũng có ErrorCode bất động sản. Cách thay thế sẽ là một cái gì đó như thế:

try
{
    SomeLegacyComOperation();
}
catch (COMException e)
{
    if (e.ErrorCode == 0x1234)
    {
        // Handle error
    }
    else
    {
        throw;
    }
}

được cho là ít thanh lịch và hơi phá vỡ dấu vết ngăn xếp.

Ngoài ra, bạn có thể đề cập đến cùng kiểu ngoại lệ hai lần trong cùng một khối try-catch:

try
{
    SomeLegacyComOperation();
}
catch (COMException e) when (e.ErrorCode == 0x1234)
{
    ...
}
catch (COMException e) when (e.ErrorCode == 0x5678)
{
    ...
}

mà sẽ không thể nếu không có when điều kiện.


82
2017-07-21 07:34



Cách tiếp cận thứ hai cũng không cho phép bắt nó theo cách khác catch, Phải không? - Tim Schmelter
@TimSchmelter. Thật. Bạn sẽ phải xử lý tất cả các COMExceptions trong cùng một khối. - Heinzi
Trong khi when cho phép bạn xử lý cùng một loại ngoại lệ nhiều lần. Bạn nên đề cập đến điều đó vì nó là một sự khác biệt rất quan trọng. Không có when bạn sẽ gặp lỗi trình biên dịch. - Tim Schmelter
Theo như tôi quan tâm, phần sau "Tóm lại:" phải là dòng đầu tiên của câu trả lời. - CompuChip
@ user3493289: đó thường là trường hợp với mã xấu xí-ass mặc dù. Bạn nghĩ rằng "Tôi không nên ở trong mớ hỗn độn này ngay từ đầu, thiết kế lại mã", và bạn cũng nghĩ "có thể có một cách để hỗ trợ thiết kế này một cách tao nhã, thiết kế lại ngôn ngữ". Trong trường hợp này có một loại ngưỡng cho bạn xấu xí như thế nào bạn muốn tập hợp các điều khoản bắt của bạn, vì vậy một cái gì đó mà làm cho tình huống nhất định ít xấu xí cho phép bạn nhận được thực hiện nhiều hơn trong ngưỡng của bạn :-) - Steve Jessop


Từ Roslyn's wiki (tôi nhấn mạnh):

Bộ lọc ngoại lệ thích hợp hơn khi bắt và quay lại vì   họ rời khỏi ngăn xếp không hề hấn gì. Nếu ngoại lệ sau đó gây ra ngăn xếp   để được bán phá giá, bạn có thể thấy nơi ban đầu nó đến từ đâu, thay vì   chỉ là nơi cuối cùng nó đã được rethrown.

Nó cũng là một dạng "lạm dụng" phổ biến và được chấp nhận để sử dụng ngoại lệ   bộ lọc cho các tác dụng phụ; ví dụ. ghi nhật ký. Họ có thể kiểm tra một ngoại lệ   "Bay bằng" mà không chặn khóa học của nó. Trong những trường hợp đó,   bộ lọc thường sẽ là cuộc gọi đến hàm trợ giúp trả về sai   thực hiện các tác dụng phụ:

private static bool Log(Exception e) { /* log it */ ; return false; }

… try { … } catch (Exception e) when (Log(e)) { }

Điểm đầu tiên đáng để chứng minh.

static class Program
{
    static void Main(string[] args)
    {
        A(1);
    }

    private static void A(int i)
    {
        try
        {
            B(i + 1);
        }
        catch (Exception ex)
        {
            if (ex.Message != "!")
                Console.WriteLine(ex);
            else throw;
        }
    }

    private static void B(int i)
    {
        throw new Exception("!");
    }
}

Nếu chúng ta chạy nó trong WinDbg cho đến khi ngoại lệ được nhấn và in chồng bằng cách sử dụng !clrstack -i -a chúng ta sẽ thấy chỉ là khung A:

003eef10 00a7050d [DEFAULT] Void App.Program.A(I4)

PARAMETERS:
  + int i  = 1

LOCALS:
  + System.Exception ex @ 0x23e3178
  + (Error 0x80004005 retrieving local variable 'local_1')

Tuy nhiên, nếu chúng ta thay đổi chương trình để sử dụng when:

catch (Exception ex) when (ex.Message != "!")
{
    Console.WriteLine(ex);
}

Chúng ta sẽ thấy stack cũng chứa Bkhung của:

001af2b4 01fb05aa [DEFAULT] Void App.Program.B(I4)

PARAMETERS:
  + int i  = 2

LOCALS: (none)

001af2c8 01fb04c1 [DEFAULT] Void App.Program.A(I4)

PARAMETERS:
  + int i  = 1

LOCALS:
  + System.Exception ex @ 0x2213178
  + (Error 0x80004005 retrieving local variable 'local_1')

Thông tin đó có thể rất hữu ích khi gỡ lỗi các bãi rác.


33
2017-07-21 07:47



Điều đó làm tôi ngạc nhiên. Sẽ không throw; (như trái ngược với throw ex;) để ngăn xếp không bị hấn gì? 1 cho tác dụng phụ. Tôi không chắc là tôi chấp nhận điều đó, nhưng thật tốt khi biết về kỹ thuật đó. - Heinzi
Nó không sai - điều này không ám chỉ đến ngăn xếp dấu vết - nó đề cập đến chính ngăn xếp. Nếu bạn nhìn vào ngăn xếp trong trình gỡ rối (WinDbg) và thậm chí nếu bạn đã sử dụng throw;, ngăn xếp thư giãn và bạn mất các giá trị tham số. - Eli Arbel
Điều này có thể cực kỳ hữu ích khi gỡ lỗi các bãi chứa. - Eli Arbel
@Heinzi See câu trả lời của tôi trong một chủ đề khác nơi bạn có thể thấy rằng throw; thay đổi dấu vết ngăn xếp một chút và throw ex; thay đổi nó rất nhiều. - Jeppe Stig Nielsen
Sử dụng throw không làm phiền dấu vết ngăn xếp một chút. Số dòng khác nhau khi sử dụng throw như trái ngược với when. - mike z


Khi một ngoại lệ được ném, thông báo xử lý ngoại lệ đầu tiên xác định nơi ngoại lệ sẽ bị bắt trước mở ngăn xếp; nếu / khi vị trí "bắt" được xác định, tất cả các khối "cuối cùng" được chạy (lưu ý rằng nếu một ngoại lệ thoát khỏi khối "cuối cùng", việc xử lý ngoại lệ trước đó có thể bị bỏ qua). Khi điều đó xảy ra, mã sẽ tiếp tục thực hiện tại "bắt".

Nếu có một điểm ngắt trong một hàm được đánh giá là một phần của "khi", điểm ngắt đó sẽ tạm ngưng thực hiện trước khi xảy ra bất kỳ sự tắc nghẽn ngăn xếp nào; ngược lại, một điểm dừng tại một "bắt" sẽ chỉ tạm ngưng thực hiện sau khi tất cả finally xử lý đã chạy.

Cuối cùng, nếu các dòng 23 và 27 của foo gọi điện barvà cuộc gọi trên dòng 23 ném một ngoại lệ bị bắt trong phạm vi foo và được rethrown trên dòng 57, sau đó theo dõi stack sẽ gợi ý rằng ngoại lệ xảy ra khi gọi bar từ dòng 57 [vị trí của rethrow], phá hủy bất kỳ thông tin nào về việc liệu ngoại lệ có xảy ra trong cuộc gọi line-23 hay line-27 hay không. Sử dụng when để tránh bắt một ngoại lệ ở nơi đầu tiên tránh sự xáo trộn như vậy.

BTW, một mẫu hữu ích gây khó chịu trong cả C # và VB.NET là sử dụng một cuộc gọi hàm trong một when mệnh đề để thiết lập một biến có thể được sử dụng trong một finally để xác định xem hàm đã hoàn thành bình thường chưa, để xử lý các trường hợp mà một hàm không có hy vọng "giải quyết" bất kỳ ngoại lệ nào xảy ra nhưng phải thực hiện hành động dựa trên nó. Ví dụ, nếu một ngoại lệ được ném trong một phương thức factory được cho là trả về một đối tượng đóng gói tài nguyên, bất kỳ tài nguyên nào đã được mua sẽ cần phải được giải phóng, nhưng ngoại lệ cơ bản sẽ thấm nhuần đến người gọi. Cách sạch nhất để xử lý ngữ nghĩa đó (mặc dù không phải cú pháp) là có một finally khối kiểm tra xem một ngoại lệ đã xảy ra và, nếu có, hãy giải phóng tất cả các tài nguyên có được thay mặt cho đối tượng không còn được trả lại. Kể từ khi mã dọn dẹp không có hy vọng giải quyết bất kỳ điều kiện nào gây ra ngoại lệ, nó thực sự không nên catch nó, nhưng chỉ cần biết chuyện gì đã xảy ra. Gọi một hàm như:

bool CopySecondArgumentToFirstAndReturnFalse<T>(ref T first, T second)
{
  first = second;
  return false;
}

trong một when điều khoản sẽ làm cho nó có thể cho chức năng của nhà máy để biết điều gì đó đã xảy ra.


5
2017-07-21 15:01