Mã không thể truy cập, nhưng có thể truy cập với một ngoại lệ


108

Mã này là một phần của ứng dụng đọc và ghi vào cơ sở dữ liệu được kết nối ODBC. Nó tạo một bản ghi trong cơ sở dữ liệu và sau đó kiểm tra xem một bản ghi đã được tạo thành công hay chưa, sau đó trả về true.

Hiểu biết của tôi về luồng điều khiển như sau:

command.ExecuteNonQuery()được ghi lại để ném một Invalid​Operation​Exceptionkhi "một cuộc gọi phương thức không hợp lệ cho trạng thái hiện tại của đối tượng". Do đó, nếu điều đó xảy ra, việc thực thi trykhối sẽ dừng lại, finallykhối sẽ được thực thi, sau đó sẽ thực thi return false;ở dưới cùng.

Tuy nhiên, IDE của tôi tuyên bố rằng return false;mã không thể truy cập được. Và nó có vẻ là sự thật, tôi có thể gỡ bỏ nó và nó biên dịch mà không có bất kỳ phàn nàn nào. Tuy nhiên, đối với tôi, có vẻ như sẽ không có giá trị trả về cho đường dẫn mã mà ngoại lệ được đề cập được ném ra.

private static bool createRecord(String table,
                                 IDictionary<String,String> data,
                                 System.Data.IDbConnection conn,
                                 OdbcTransaction trans) {

    [... some other code ...]

    int returnValue = 0;
    try {
        command.CommandText = sb.ToString();
        returnValue = command.ExecuteNonQuery();

        return returnValue == 1;
    } finally {
        command.Dispose();
    }

    return false;
}

Tôi hiểu sai ở đây là gì?



41
Lưu ý phụ: không gọi Disposemột cách rõ ràng, nhưng đặt using:using (var command = ...) {command.CommandText = sb.ToString(); return command.ExecuteNonQuery(); }
Dmitry Bychenko

7
Một finallykhối có nghĩa là một cái gì đó khác hơn bạn nghĩ.
Thorbjørn Ravn Andersen

Câu trả lời:


149

Cảnh báo trình biên dịch (mức 2) CS0162

Đã phát hiện mã không thể truy cập

Trình biên dịch phát hiện mã sẽ không bao giờ được thực thi.

Chỉ nói rằng, Trình biên dịch đủ hiểu thông qua Phân tích tĩnh rằng nó không thể đạt được và hoàn toàn loại bỏ nó khỏi IL đã biên dịch (do đó, cảnh báo của bạn)

Lưu ý : Bạn có thể tự chứng minh sự thật này bằng cách thử Bước tới Mã không thể truy cập bằng trình gỡ lỗi hoặc sử dụng IL Explorer

finallythể chạy trên một Ngoại lệ , (mặc dù điều đó sang một bên), nó không thay đổi thực tế (trong trường hợp này), nó vẫn sẽ là một Ngoại lệ Chưa được suy nghĩ . Ergo, người cuối cùng returnsẽ không bao giờ bị đánh bất kể.

  • Nếu bạn muốn mã tiếp tục đến mã cuối cùng return, lựa chọn duy nhất của bạn là Catch the Exception ;

  • Nếu không, bạn cứ để nguyên như vậy và xóa dấu return.

Thí dụ

try 
{
    command.CommandText = sb.ToString();
    returnValue = command.ExecuteNonQuery();

    return returnValue == 1;
}
catch(<some exception>)
{
   // do something
}
finally 
{
    command.Dispose();
}

return false;

Để trích dẫn tài liệu

try-last (Tham chiếu C #)

Bằng cách sử dụng khối cuối cùng, bạn có thể dọn dẹp bất kỳ tài nguyên nào được phân bổ trong khối thử và bạn có thể chạy mã ngay cả khi một ngoại lệ xảy ra trong khối thử. Thông thường, các câu lệnh của một khối cuối cùng chạy khi điều khiển để lại một câu lệnh try. Việc chuyển giao quyền kiểm soát có thể xảy ra do thực hiện bình thường, thực hiện câu lệnh break, continue, goto, hoặc return, hoặc sự lan truyền một ngoại lệ ra khỏi câu lệnh try.

Trong một ngoại lệ được xử lý, khối cuối cùng được liên kết được đảm bảo sẽ chạy. Tuy nhiên, nếu ngoại lệ không được xử lý, việc thực thi khối cuối cùng phụ thuộc vào cách kích hoạt hoạt động rút lại ngoại lệ. Điều đó phụ thuộc vào cách máy tính của bạn được thiết lập.

Thông thường, khi một ngoại lệ chưa được xử lý kết thúc một ứng dụng, việc khối cuối cùng có được chạy hay không là điều không quan trọng. Tuy nhiên, nếu bạn có các câu lệnh trong một khối cuối cùng phải được chạy ngay cả trong tình huống đó, một giải pháp là thêm một khối bắt vào câu lệnh try-last . Ngoài ra, bạn có thể nắm bắt ngoại lệ có thể được đưa vào khối try của câu lệnh try-last cao hơn trong ngăn xếp cuộc gọi . Nghĩa là, bạn có thể bắt ngoại lệ trong phương thức gọi phương thức chứa câu lệnh try-last hoặc trong phương thức gọi phương thức đó hoặc trong bất kỳ phương thức nào trong ngăn xếp cuộc gọi. Nếu ngoại lệ không được bắt, việc thực thi khối cuối cùng phụ thuộc vào việc hệ điều hành có chọn kích hoạt thao tác rút lại ngoại lệ hay không.

Cuối cùng

Khi sử dụng bất kỳ thứ gì hỗ trợ IDisposablegiao diện (được thiết kế để giải phóng các tài nguyên không được quản lý), bạn có thể gói nó trong một usingcâu lệnh. Trình biên dịch sẽ tạo ra một try {} finally {}lệnh gọi nội bộ Dispose()đối tượng


1
Ý bạn là gì của IL trong những câu đầu tiên?
Cỗ máy đồng hồ

2
@Clockwork IL là một sản phẩm của quá trình biên dịch mã được viết bằng ngôn ngữ .NET cấp cao. Khi bạn biên dịch mã của mình được viết bằng một trong những ngôn ngữ này, bạn sẽ nhận được một tệp nhị phân được tạo ra từ IL. . Lưu ý rằng Intermediate Language đôi khi còn gọi là Common Intermediate Language (CIL) hoặc Microsoft Intermediate Language (MSIL)
TheGeneral

1
Nói một cách ngắn gọn, bởi vì anh ta không nắm bắt được các khả năng là: Hoặc là thử chạy cho đến khi nó trả về và do đó bỏ qua kết quả bên dưới HOẶC một ngoại lệ được ném ra và không bao giờ đạt được trả về vì hàm sẽ thoát do một ngoại lệ ném.
Felype

86

khối cuối cùng sẽ được thực thi, sau đó sẽ thực thi trả về false; ở dưới cùng.

Sai lầm. finallykhông nuốt ngoại lệ. Nó tôn vinh nó và ngoại lệ sẽ được ném như bình thường. Nó sẽ chỉ thực thi mã cuối cùng trước khi khối kết thúc (có hoặc không có ngoại lệ).

Nếu bạn muốn ngoại lệ bị nuốt, bạn nên sử dụng một catchkhối không có throwtrong đó.


1
sẽ biên dịch sinppet trên trong trường hợp ngoại lệ, những gì sẽ được trả về?
Ehsan Sajjad

3
Nó thực hiện biên dịch, nhưng nó sẽ không bao giờ đánh return falsevì nó sẽ ném một ngoại lệ thay @EhsanSajjad
Patrick Hofman

1
có vẻ lạ, biên dịch bởi vì nó sẽ trả về giá trị cho bool trong trường hợp không có ngoại lệ và trong trường hợp ngoại lệ sẽ không có gì, vậy hợp pháp để thỏa mãn kiểu trả về của phương thức?
Ehsan Sajjad

2
Trình biên dịch sẽ chỉ bỏ qua dòng, đó là những gì cảnh báo dành cho. Vậy tại sao lạ vậy? @EhsanSajjad
Patrick Hofman

3
Sự thật thú vị: Nó thực sự không được đảm bảo rằng một khối cuối cùng sẽ chạy nếu ngoại lệ không được bắt trong chương trình. Thông số kỹ thuật không đảm bảo điều này và các CLR sớm KHÔNG thực hiện khối cuối cùng. Tôi nghĩ rằng bắt đầu với 4.0 (có thể là sớm hơn) hành vi đó đã thay đổi, nhưng các thời gian chạy khác vẫn có thể hoạt động khác. Làm cho hành vi khá đáng ngạc nhiên.
Voo

27

Cảnh báo là do bạn không sử dụng catchvà phương pháp của bạn về cơ bản được viết như thế này:

bool SomeMethod()
{
    return true;
    return false; // CS0162 Unreachable code detected
}

Vì bạn finallychỉ sử dụng để vứt bỏ, giải pháp ưu tiên là sử dụng usingmẫu:

using(var command = new WhateverCommand())
{
     ...
}

Đó là đủ, để đảm bảo những gì Disposesẽ được gọi. Nó được đảm bảo sẽ được gọi sau khi thực hiện thành công khối mã hoặc khi (trước) một số catch lỗi trong ngăn xếp cuộc gọi (lệnh gọi mẹ không hoạt động, phải không?).

Nếu nó không phải là về xử lý, thì

try { ...; return true; } // only one return
finally { ... }

là đủ, vì bạn sẽ không bao giờ phải trả về falseở cuối phương thức (không cần dòng đó). Phương thức của bạn là kết quả trả về của việc thực thi lệnh ( truehoặc false) hoặc sẽ đưa ra một ngoại lệ nếu không .


Ngoài ra, hãy cân nhắc để ném các ngoại lệ của riêng mình bằng cách gói các ngoại lệ dự kiến ​​(kiểm tra hàm tạo không hợp lệ ):

try { ... }
catch(SomeExpectedException e)
{
    throw new SomeBetterExceptionWithExplanaition("...", e);
}

Điều này thường được sử dụng để nói điều gì đó có ý nghĩa hơn (hữu ích) đối với người gọi hơn là ngoại lệ cuộc gọi lồng nhau sẽ nói.


Hầu hết các lần bạn không thực sự quan tâm đến những trường hợp ngoại lệ không được giải quyết. Đôi khi bạn cần đảm bảo rằng nó finallyđược gọi ngay cả khi ngoại lệ được xử lý. Trong trường hợp này, bạn chỉ cần tự mình nắm bắt và ném lại (xem câu trả lời này ):

try { ... }
catch { ...; throw; } // re-throw
finally { ... }

14

Có vẻ như bạn đang tìm kiếm một thứ như thế này:

private static bool createRecord(string table,
                                 IDictionary<String,String> data,
                                 System.Data.IDbConnection conn,
                                 OdbcTransaction trans) {
  [... some other code ...]

  // Using: do not call Dispose() explicitly, but wrap IDisposable into using
  using (var command = ...) {
    try {
      // Normal flow:
      command.CommandText = sb.ToString();

      // True if and only if exactly one record affected
      return command.ExecuteNonQuery() == 1;
    }
    catch (DbException) {
      // Exceptional flow (all database exceptions)
      return false;
    }
  }
}

Xin lưu ý, điều finally đó không nuốt bất kỳ ngoại lệ nào

finally {
  // This code will be executed; the exception will be efficently re-thrown
}

// And this code will never be reached

8

Bạn không có catchkhối, vì vậy ngoại lệ vẫn được ném ra, điều này sẽ chặn trả về.

khối cuối cùng sẽ được thực thi, sau đó sẽ thực thi trả về false; ở dưới cùng.

Điều này là sai, vì khối cuối cùng sẽ được thực thi, và sau đó sẽ có một ngoại lệ không cần thiết.

finallycác khối được sử dụng để dọn dẹp và chúng không bắt được ngoại lệ. Ngoại lệ được ném trước khi trả về, do đó, sẽ không bao giờ đạt được trả về, vì một ngoại lệ được ném trước đó.

IDE của bạn đúng là nó sẽ không bao giờ đạt được, vì ngoại lệ sẽ bị ném ra. Chỉ cócatch các khối có thể bắt được các ngoại lệ.

Đọc từ tài liệu ,

Thông thường, khi một ngoại lệ chưa được xử lý kết thúc một ứng dụng, việc khối cuối cùng có được chạy hay không là điều không quan trọng. Tuy nhiên, nếu bạn có các câu lệnh trong một khối cuối cùng phải được chạy ngay cả trong tình huống đó, một giải pháp là thêm một khối bắt vào câu lệnh try-last . Ngoài ra, bạn có thể nắm bắt ngoại lệ có thể được đưa vào khối try của câu lệnh try-last cao hơn trong ngăn xếp cuộc gọi. Nghĩa là, bạn có thể bắt ngoại lệ trong phương thức gọi phương thức chứa câu lệnh try-last hoặc trong phương thức gọi phương thức đó hoặc trong bất kỳ phương thức nào trong ngăn xếp cuộc gọi. Nếu ngoại lệ không được bắt, việc thực thi khối cuối cùng phụ thuộc vào việc hệ điều hành có chọn kích hoạt thao tác rút lại ngoại lệ hay không .

Điều này cho thấy rõ ràng rằng cuối cùng không nhằm mục đích bắt ngoại lệ và bạn sẽ đúng nếu có một catchcâu lệnh trống trước finallycâu lệnh.


7

Khi ngoại lệ được ném, ngăn xếp sẽ rút lại (thực thi sẽ di chuyển ra khỏi hàm) mà không trả về giá trị và bất kỳ khối bắt nào trong khung ngăn xếp phía trên hàm sẽ bắt ngoại lệ thay thế.

Do đó, return falsesẽ không bao giờ thực thi.

Hãy thử ném một ngoại lệ theo cách thủ công để hiểu quy trình điều khiển:

try {
    command.CommandText = sb.ToString();
    returnValue = command.ExecuteNonQuery();

    // Try this.
    throw new Exception("See where this goes.");

    return returnValue == 1;
} finally {
    command.Dispose();
}

5

Trên mã của bạn:

private static bool createRecord(String table, IDictionary<String,String> data, System.Data.IDbConnection conn, OdbcTransaction trans) {

    [... some other code ...]

    int returnValue = 0;
    try {
        command.CommandText = sb.ToString();
        returnValue = command.ExecuteNonQuery();

        return returnValue == 1; // You return here in case no exception is thrown
    } finally {
        command.Dispose(); //You don't have a catch so the exception is passed on if thrown
    }

    return false; // This is never executed because there was either one of the above two exit points of the method reached.
}

khối cuối cùng sẽ được thực thi, sau đó sẽ thực thi trả về false; ở dưới cùng

Đây là lỗ hổng trong logic của bạn vì finallykhối sẽ không bắt được ngoại lệ và nó sẽ không bao giờ đạt được câu lệnh trả về cuối cùng.


4

Không return falsethể truy cập câu lệnh cuối cùng , bởi vì khối try thiếu một catchphần có thể xử lý ngoại lệ, vì vậy ngoại lệ được phát triển lại sau finallykhối và việc thực thi không bao giờ đạt đến câu lệnh cuối cùng.


2

Bạn có hai đường dẫn trả lại trong mã của mình, đường dẫn thứ hai không thể truy cập được vì đường dẫn đầu tiên. Câu lệnh cuối cùng trong trykhối của bạn return returnValue == 1;cung cấp lợi nhuận bình thường của bạn, vì vậy bạn không bao giờ có thể đạt đượcreturn false; cuối khối phương thức.

FWIW, thứ tự loại trừ liên quan đến finally khối là: biểu thức cung cấp giá trị trả về trong khối try sẽ được đánh giá đầu tiên, sau đó khối cuối cùng sẽ được thực thi, và sau đó giá trị biểu thức được tính toán sẽ được trả về (bên trong khối try).

Liên quan đến luồng trên ngoại lệ ... không có a catch, finallysẽ được thực thi theo ngoại lệ trước khi ngoại lệ sau đó được phát triển lại khỏi phương thức; không có con đường "trở lại".

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.