Nắm bắt ngoại lệ với "bắt, khi nào"


94

Tôi đã tìm thấy tính năng mới này trong C # cho phép một trình xử lý bắt 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à 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à điều 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 rằng không.

Một kịch bản khác mà tôi có thể nghĩ ra 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à điều 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;
}

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 vì trình xử lý bị bỏ qua như vậy và việc mở ngăn xếp có thể xảy ra sớm hơn nhiều so với việc xử lý các trường hợp sử dụng cụ thể trong trình xử lý không? Có bất kỳ trường hợp sử dụng cụ thể nào phù hợp hơn với tính năng này mà mọi người có thể áp dụng như một phương pháp hay không?


8
Nó rất hữu ích nếu whennhu cầu để truy cập ngoại lệ bản thân
Tim Schmelter

1
Nhưng đó là điều chúng ta có thể làm trong chính khối trình xử lý. Có lợi ích nào ngoài 'mã có tổ chức hơn một chút' không?
MS Srikkanth

3
Nhưng sau đó bạn đã xử lý ngoại lệ mà bạn không muốn. Điều gì xảy ra nếu bạn muốn bắt nó ở một nơi khác trong này try..catch...catch..catch..finally?
Tim Schmelter

4
@ user3493289: Theo lập luận đó, chúng tôi cũng không cần kiểm tra kiểu automatich 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à thrownếu không. Mã có tổ chức hơn một chút (hay còn gọi là tránh nhiễu mã) chính là lý do tại sao tính năng này tồn tại. (Đây là thực sự đúng đối với rất nhiều tính năng.)
Heinzi

2
@TimSchmelter Cảm ơn. Đăng nó như một câu trả lời và tôi sẽ chấp nhận nó. Vì vậy, các kịch bản thực tế sau đó sẽ là 'nếu điều kiện để xử lý phụ thuộc vào ngoại lệ', sau đó sử dụng tính năng này /
MS Srikkanth

Câu trả lời:


118

Các khối bắt đã cho phép bạn lọc loại ngoại lệ:

catch (SomeSpecificExceptionType e) {...}

Các whenkhoản cho phép bạn mở rộng bộ lọc này để biểu chung chung.

Do đó, bạn sử dụng whenmệnh đề cho các trường hợp mà loại ngoại lệ không đủ khác biệt để xác định xem ngoại lệ có nên được xử lý ở đây hay không.


Một trường hợp sử dụng phổ biến là các loại ngoại lệ thực sự là một trình bao bọc 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 khá lâu):

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

Tương tự cho SqlException, cũng có một thuộc ErrorCodetính. Sự 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à kém 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 một loại ngoại lệ hai lần trong cùng một khối try-catch-block:

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

sẽ không thể thực hiện được nếu không có whenđiều kiện.


2
Cách tiếp cận thứ hai cũng không cho phép bắt nó theo một 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 các whencho phép bạn xử lý cùng một loại ngoại lệ nhiều lần. Bạn cũng nên đề cập đến điều đó vì đó là một sự khác biệt quan trọng. Nếu không, whenbạn sẽ gặp lỗi trình biên dịch.
Tim Schmelter,

1
Theo như tôi lo lắng, phần sau "Tóm lại:" nên là dòng đầu tiên của câu trả lời.
CompuChip

1
@ user3493289: đó là trường hợp thường xảy ra với mã xấu xí. 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 thanh lịch, thiết kế lại ngôn ngữ". Trong trường hợp này có một loại ngưỡng cho cách xấu xí mà bạn muốn thiết lập lại mệnh đề catch được, 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 được nhiều việc hơn trong ngưỡng của bạn :-)
Steve Jessop

37

Từ wiki của Roslyn (tôi nhấn mạnh):

Các bộ lọc ngoại lệ thích hợp hơn để bắt và ném lại vì chúng giúp ngăn xếp không hề hấn gì . Nếu ngoại lệ sau đó khiến ngăn xếp bị đổ, bạn có thể xem nó xuất phát từ đâu, thay vì chỉ nơi cuối cùng nó được trồng lại.

Nó cũng là một hình thức “lạm dụng” phổ biến và được chấp nhận để sử dụng các bộ lọc ngoại lệ 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 ngang qua” mà không cản trở đường đi của nó . Trong những trường hợp đó, bộ lọc thường sẽ là một lệnh gọi đến một hàm trợ giúp trả về sai thực thi 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 rất đá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 tôi chạy điều này trong WinDbg cho đến khi ngoại lệ được nhấn và in ngăn xếp bằng cách sử dụng, !clrstack -i -achúng tôi sẽ thấy khung của 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 tôi 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 ngăn xếp 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 kết xuất sự cố.


7
Điều đó làm tôi ngạc nhiên. Cũng sẽ không throw;(trái ngược với throw ex;) để ngăn xếp không hề hấn gì? +1 cho điều tác dụng phụ. Tôi không chắc mình đồng ý với điều đó, nhưng thật tốt nếu biết về kỹ thuật đó.
Heinzi

13
Nó không sai - điều này không đề cập đến dấu vết ngăn xếp - 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ỡ lỗi (WinDbg) và ngay cả khi bạn đã sử dụng throw;, ngăn xếp sẽ mở và bạn mất các giá trị tham số.
Eli Arbel

1
Điều này có thể cực kỳ hữu ích khi gỡ lỗi kết xuất.
Eli Arbel

3
@Heinzi Xem câu trả lời của tôi trong một chủ đề khác , nơi bạn có thể thấy điều đó throw;thay đổi dấu vết ngăn xếp một chút và throw ex;thay đổi nhiều.
Jeppe Stig Nielsen,

1
Sử dụng throwcó làm xáo trộn dấu vết ngăn xếp một chút. Số dòng khác nhau khi sử dụng throwtrái ngược với when.
Mike Zboray

7

Khi một ngoại lệ được ném ra, lần xử lý ngoại lệ đầu tiên xác định vị trí mà ngoại lệ sẽ bị bắt trước khi giải nén 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" sẽ đượ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 thi tại "catch".

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 nào", thì điểm ngắt đó sẽ tạm dừng việc thực thi trước khi xảy ra bất kỳ thao tác gỡ ngăn xếp nào; ngược lại, một điểm ngắt tại một "catch" sẽ chỉ tạm ngừng thực thi sau khi tất cả các finallytrình xử lý đã chạy.

Cuối cùng, nếu dòng 23 và 27 của foocuộc gọi barvà cuộc gọi trên dòng 23 ném một ngoại lệ được bắt bên trong foovà phát triển lại ở dòng 57, thì dấu vết ngăn xếp sẽ gợi ý rằng ngoại lệ đã xảy ra trong khi gọi bartừ dòng 57 [vị trí của rethrow] , hủy bất kỳ thông tin nào về việc liệu trường hợp ngoại lệ xảy ra trong cuộc gọi đường dây-23 hay đường dây-27. Việc sử dụng whenđể tránh bắt một ngoại lệ ngay từ đầu sẽ tránh được 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 lệnh gọi hàm trong một whenmệnh đề để đặt một biến có thể được sử dụng trong một finallymệnh đề để xác định xem hàm có hoàn thành bình thường hay không, để xử lý các trường hợp 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 dù sao cũng phải hành động dựa trên nó. Ví dụ: nếu một ngoại lệ được đưa ra 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, thì bất kỳ tài nguyên nào đã có được sẽ cần được giải phóng, nhưng ngoại lệ cơ bản sẽ được truyền đến người gọi. Cách rõ ràng nhất để xử lý điều đó về mặt ngữ nghĩa (mặc dù không phải về mặt cú pháp) là cófinallychặn kiểm tra xem có ngoại lệ xảy ra hay không 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. Vì 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 làm catchvậy, mà chỉ cần biết điều 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 whenmệnh đề sẽ làm cho chức năng của nhà máy có thể biết rằng đã có điều gì đó xảy ra.

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.