2010-10-22 4 views
47

Chỉ cần tò mò: Tại sao cú pháp cho try catch in C# (Java cũng?) Được mã hóa cứng cho nhiều câu lệnh? Tại sao ngôn ngữ không cho phép:Tại sao Try-Catch yêu cầu dấu ngoặc nhọn

int i; 
string s = DateTime.Now.Seconds % 2 == 1 ? "1" : "not 1"; 
try 
    i = int.Parse(s); 
catch 
    i = 0; 

Ví dụ này chỉ dành cho mục đích tầm thường. Tôi biết có int.TryParse.

+5

Eh, bởi vì các nhà thiết kế của một trong hai ngôn ngữ cảm thấy như thế này là không cần thiết? –

+1

Yêu cầu tính năng http://connect.microsoft.com/ –

+10

Đó là âm mưu của các nhà thiết kế ngôn ngữ giúp bạn sử dụng phong cách cú đúp K & R. – zwol

Trả lời

64

xem xét thực tế rằng có thực sự ba (hoặc nhiều hơn) khối mã các học trò ở đây:

try {} 
catch (myexcption) 
{} 
catch (myotherexception) 
{} 
finally 
{} 

Hãy nhớ rằng những điều này nằm trong phạm vi của một ngữ cảnh lớn hơn và các trường hợp ngoại lệ không bị bắt được đẩy mạnh hơn nữa lên ngăn xếp.

Lưu ý rằng điều này về cơ bản giống với cấu trúc lớp học cũng có cấu trúc {}.

Nói ví dụ bạn có thể có:

try 
try 
if (iAmnotsane) 
beatMe(please); 
catch (Exception myexception) 
catch (myotherexception) 
logerror("howdy") 
finally 

VỚI DOANH NGHIỆP nào đó bắt thứ hai thuộc về người đầu tiên hoặc thứ hai thử? Còn cuối cùng thì sao? SO bạn thấy các phần tùy chọn/nhiều làm cho yêu cầu.

+8

+1 Để cung cấp mã không thể đọc được :) Điều đó đang được nói, các khối IF-ELSE lồng nhau có cùng vấn đề. – Brian

+15

IF-ELSE là khác nhau, vì có nhiều nhất một ELSE trong IF-ELSE, trong khi có 0 hoặc nhiều CATCH trong TRY-CATCH. –

+0

Chính xác của Brian. Mã không đọc được là tuyệt vời. nếu người nào khác có cùng một vấn đề nếu bạn có if-if-statement-else (không có dấu ngoặc nhọn), thì cái kia thì mơ hồ mà nó thuộc về. Theo quy ước, tôi nghĩ nó thuộc về thứ hai nếu. – Shlomo

0

Các hợp lý là nó dễ bảo trì hơn (dễ dàng hơn để thay đổi, ít có khả năng để phá vỡ, ergo chất lượng cao hơn):

  1. nó rõ ràng hơn, và
  2. nó dễ dàng hơn để thay đổi bởi vì nếu bạn cần phải thêm một đường vào các khối của bạn, bạn không giới thiệu một lỗi.

Là tại sao xử lý ngoại lệ khác với biểu thức có điều kiện ...

  • Nếu/khác có điều kiện thuận một biểu thức để sử dụng một trong hai (hoặc nhiều hơn Nếu/Else if/else) đường dẫn trong mã số
  • Thử/Catch là một phần của xử lý ngoại lệ, nó không phải là biểu thức có điều kiện. Try/Catch/Cuối cùng chỉ hoạt động khi một ngoại lệ đã được ném vào bên trong phạm vi của khối Thử.

Xử lý ngoại lệ sẽ di chuyển lên chồng/phạm vi cho đến khi tìm thấy khối Bắt giữ sẽ loại ngoại lệ được ném. Việc buộc các số nhận dạng phạm vi làm cho việc kiểm tra các khối này được đơn giản hóa. Buộc bạn phạm vi khi đối phó với các ngoại lệ có vẻ như là một ý tưởng hay, nó cũng là một dấu hiệu tốt cho thấy đây là một phần của xử lý ngoại lệ chứ không phải là mã bình thường. Ngoại lệ là ngoại lệ, không phải là điều bạn thực sự muốn xảy ra bình thường nhưng biết có thể xảy ra và muốn xử lý khi chúng xảy ra.

EDIT: Có một lý do nữa mà tôi có thể nghĩ đến, đó là CATCH là bắt buộc sau TRY không giống như ELSE. Do đó cần phải có cách xác định để xác định khối TRY.

+2

Tôi không đồng ý với sự tương tự của bạn. Các câu lệnh 'lock' và' using' không yêu cầu cú pháp khối '{}', và chúng không phải là "có điều kiện". –

+0

@Kirk - Có thể bạn nói đúng, nhưng đây chỉ là suy nghĩ của tôi về câu hỏi này. –

+1

"... cần phải có một cách xác định để xác định khối TRY": có, nhưng điều đó không có nghĩa là cách đó phải sử dụng dấu ngoặc nhọn. Sau đây là mã C# hợp lệ: 'do i = 1+ 1; trong khi (đúng); Đâu là kết thúc của khối 'do'? –

0

Có lẽ là không khuyến khích lạm dụng. Một khối try-catch là lớn và xấu xí, và bạn sẽ nhận thấy khi bạn đang sử dụng nó. Điều này phản ánh ảnh hưởng của việc nắm bắt hiệu suất ứng dụng của bạn - bắt một ngoại lệ cực kỳ chậm so với một thử nghiệm boolean đơn giản.

Nói chung, bạn nên tránh lỗi, không xử lý chúng. Trong ví dụ bạn đưa ra, một phương pháp hiệu quả hơn sẽ là sử dụng

if(!int.TryParse(s, out i)) 
i=0; 
+1

Luôn tránh lỗi, vâng. Nhưng có những lỗi có thể xảy ra bất kể bạn cẩn thận như thế nào hoặc bạn thực hiện bao nhiêu séc (ví dụ: khi viết mã giao dịch với các thiết bị IO như hệ thống tệp hoặc mạng). –

+5

Tôi sẵn sàng đặt cược cú pháp này không được phát minh bởi vì Anders nói, "Tôi tự hỏi liệu chúng ta có thể làm cho cú pháp này đủ xấu để tránh sử dụng." –

+0

@ Michael-Petito Rõ ràng chúng là một cấu trúc cần thiết, tôi chỉ không nghĩ rằng chúng cần phải được áp dụng như là giải pháp chung. – Jake

1

Điều đầu tiên tôi nghĩ là các dấu ngoặc nhọn tạo ra một khối với phạm vi biến riêng của nó.

Nhìn vào đoạn mã sau

try 
{ 
    int foo = 2; 
} 
catch (Exception) 
{ 
    Console.WriteLine(foo); // The name 'foo' does not exist in the current context 
} 

foo không thể truy cập trong khối catch do Phạm vi biến. Tôi nghĩ rằng điều này làm cho nó dễ dàng hơn để lý do về việc liệu một biến đã được khởi tạo trước khi sử dụng hay không.

Hãy so sánh với mã này

int foo; 
try 
{ 
    foo = 2; 
} 
catch (Exception) 
{ 
    Console.WriteLine(foo); // Use of unassigned local variable 'foo' 
} 

đây bạn không thể đảm bảo rằng foo được khởi tạo.

+1

Tôi nghĩ rằng đây là một lý lẽ tồi. Đối với một: a if statement ** không có ** niềng răng xoăn cũng bắt đầu một phạm vi (một dòng) mới. – Greg

+1

Trong ví dụ thứ hai của bạn, sử dụng 'foo' sau khối try/catch cũng sẽ dẫn đến lỗi trình biên dịch (vì bạn không gán foo bên trong catch). Vì vậy, nếu bạn có thể làm thử/nắm bắt mà không có dấu ngoặc nhọn, bạn sẽ phải làm theo các quy tắc tương tự như các phát biểu khối khác liên quan đến tuyên bố khai báo. –

+2

@Greg: thử khai báo và khởi tạo biến trong câu lệnh if –

-2

Câu trả lời đơn giản nhất (tôi nghĩ) là mỗi khối mã trong C/C++/C# yêu cầu dấu ngoặc nhọn.

EDIT # 1

Để đối phó với phiếu chống, trực tiếp từ MSDN:

try-catch (C# Reference)

Các câu lệnh try-catch bao gồm một thử khối theo sau là một hoặc nhiều mệnh đề bắt, chỉ định các trình xử lý cho các ngoại lệ khác nhau.

Theo định nghĩa, đó là một khối, do đó, nó yêu cầu dấu ngoặc nhọn. Đó là lý do tại sao chúng tôi không thể sử dụng nó mà không có { }.

+0

Đầu tiên, không có điều gì như C/C++/C#. Và thứ hai, câu hỏi là "tại sao đây lại là một khối"? Sau một 'while',' if', hoặc 'for' bạn có thể có một câu lệnh đơn (không có dấu ngoặc ôm), vì vậy @Shlomo hỏi tại sao đó không phải là trường hợp cho' try' trong C# và Java. –

+4

@Kate Gregory: Có thể không có những thứ như C/C++/C#, nhưng có, C++ và C#. Giống như khi chúng tôi nói Windows 95/98/NT/XP, v.v. Không có điều như vậy! Bên cạnh đó, chúng tôi hiểu rằng chúng tôi đang nói về các phiên bản khác nhau của một gia đình Windows. Đó là điều tôi muốn nói ở đây. –

+0

@Kate Gregory: Đây là một khối vì bạn có nhiều hướng dẫn trong một khối 'try'. Bạn có thể có 'try ... catch',' try ... finally' hoặc 'try ... catch ... finally'. Lệnh 'try' là một khối cho chính nó. Nếu bạn so sánh với 'if',' while', 'for', thì không có sự kết hợp khác của các lệnh này, mỗi đối lập với khối' try'. Vì vậy, 'try' chắc chắn là một khối, và khối yêu cầu dấu ngoặc nhọn. Cuối cùng, chúng tôi tóm tắt với kết luận tương tự như câu trả lời của tôi, chỉ có câu trả lời của tôi đơn giản hơn bình luận này. –

1
try // 1 
try // 2 
    something(); 
catch { // A 
} 
catch { // B 
} 
catch { // C 
} 

B bắt thử 1 hoặc 2?

Tôi không nghĩ rằng bạn có thể giải quyết này một cách rõ ràng, kể từ đoạn có thể có nghĩa:

try // 1 
{ 
    try // 2 
     something(); 
    catch { // A 
    } 
} 
catch { // B 
} 
catch { // C 
} 


try // 1 
{ 
    try // 2 
     something(); 
    catch { // A 
    } 
    catch { // B 
    } 
} 
catch { // C 
} 
47

CẬP NHẬT: Câu hỏi này là chủ đề của my blog on December 4th, 2012. Có một số nhận xét sâu sắc trên blog mà bạn cũng có thể quan tâm. Cảm ơn câu hỏi tuyệt vời!


Như những người khác đã lưu ý, tính năng được đề xuất giới thiệu sự mơ hồ gây nhầm lẫn. Tôi đã quan tâm để xem liệu có bất kỳ biện minh nào khác cho quyết định không hỗ trợ tính năng này hay không, vì vậy tôi đã kiểm tra lưu trữ ghi chú thiết kế ngôn ngữ.

Tôi không thấy gì trong lưu trữ ghi chú thiết kế ngôn ngữ để biện minh cho quyết định này. Theo như tôi biết, C# thực hiện nó theo cách đó bởi vì đó là cách các ngôn ngữ khác có cú pháp tương tự thực hiện nó, và họ làm theo cách đó vì vấn đề mơ hồ.

Tôi đã học được điều gì đó thú vị. Trong thiết kế ban đầu của C# không có cố gắng-bắt-cuối cùng!Nếu bạn muốn thử với câu trả lời và cuối cùng thì bạn phải viết:

try 
{ 
    try 
    { 
     XYZ(); 
    } 
    catch(whatever) 
    { 
    DEF(); 
    } 
} 
finally 
{ 
    ABC(); 
} 

mà, không ngạc nhiên, chính xác là cách trình biên dịch phân tích cố gắng nắm bắt cuối cùng; nó chỉ phá vỡ nó vào cố gắng nắm bắt bên trong cố gắng cuối cùng khi phân tích ban đầu và giả vờ đó là những gì bạn đã nói ở nơi đầu tiên.

+4

Cảm ơn Eric. Và cảm ơn vì sự thấu hiểu ... – Shlomo

+20

"vì vậy tôi đã kiểm tra lưu trữ ghi chú thiết kế ngôn ngữ" ... tại sao tôi lại hình dung bạn đi vào Batcave? Chỉ trên S.O. bạn sẽ nhận được một câu trả lời như thế này. Bạn đá, Eric. –

+0

Delphi (vâng, nó vẫn tồn tại) có chính xác vấn đề này: bạn phải làm 'try {try/catch} final' thay vì chỉ' try/catch/finally'. – Polynomial

8

Ít hoặc nhiều, đây là lần phát trên dangling else problem.

Ví dụ,

if(blah) 
    if (more blah) 
     // do some blah 
else 
    // no blah I suppose 

Nếu không có dấu ngoặc nhọn, những thứ khác là mơ hồ bởi vì bạn không biết nếu nó liên quan đến việc đầu tiên hoặc thứ hai nếu tuyên bố. Vì vậy, bạn phải dự phòng trên một trình biên dịch (ví dụ trong Pascal hoặc C, trình biên dịch giả định sự lúng túng khác được liên kết với câu lệnh if gần nhất) để giải quyết sự mơ hồ hoặc không biên dịch hoàn toàn nếu bạn không muốn cho phép sự mơ hồ như vậy ngay từ đầu.

Tương tự,

try 
    try 
     // some code that throws! 
catch(some blah) 
    // which try block are we catching??? 
catch(more blah) 
    // not so sure... 
finally 
    // totally unclear what try this is associated with. 

Bạn có thể giải quyết nó với một quy ước, nơi khối catch được luôn gắn liền với những thử gần nhất, nhưng tôi tìm thấy giải pháp này thường cho phép các lập trình viên để viết mã đó là nguy hiểm tiềm tàng. Ví dụ: trong C, điều này:

if(blah) 
    if(more blah) 
     x = blah; 
    else 
     x = blahblah; 

... là cách trình biên dịch sẽ hiểu điều này nếu/nếu/khối khác. Tuy nhiên, nó cũng hoàn toàn hợp pháp để vít lên thụt của bạn và viết:

if(blah) 
    if(more blah) 
     x = blah; 
else 
    x = blahblah; 

... mà bây giờ làm cho nó xuất hiện như khác được kết hợp với bên ngoài câu lệnh if, trong khi thực tế nó được kết hợp với bên trong nếu tuyên bố do các công ước C. Vì vậy, tôi nghĩ rằng yêu cầu niềng răng đi một chặng đường dài hướng tới giải quyết sự mơ hồ và ngăn chặn một lỗi khá lén lút (những loại vấn đề có thể được tầm thường để bỏ lỡ, ngay cả trong kiểm tra mã). Các ngôn ngữ như python không có vấn đề này kể từ khi thụt đầu dòng và vấn đề khoảng trắng.

+0

+1 để giới thiệu cho tôi về vấn đề khác. 4 năm của sci trong C/C++ và tôi chưa bao giờ nghe nói về điều đó. Tôi luôn luôn sử dụng phong cách K & R curlies vì ​​vậy tôi không bao giờ phải suy nghĩ về nó. Tôi thậm chí làm điều đó trên một lớp lót ... 'if (isTrue) {curlies ++; } '. – mattmc3

7

Nếu bạn cho rằng các nhà thiết kế của C# đơn giản chọn sử dụng cú pháp giống như C++ thì câu hỏi sẽ trở thành lý do tại sao dấu ngoặc đơn cần thiết với các câu lệnh đơn thử và bắt khối trong C++. Câu trả lời đơn giản là Bjarne Stroustrup cho rằng cú pháp dễ giải thích hơn.

Trong The Design and Evolution of C++ Stroustrup viết:

"Từ khóa thử là hoàn toàn không cần thiết và do đó là những {} niềng răng trừ trường hợp nhiều câu lệnh đang thực sự được sử dụng trong một thử khối hoặc một handler."

Anh ấy tiếp tục đưa ra ví dụ trong đó từ khóa thử và {} là không cần thiết. Sau đó, ông viết:

"Tuy nhiên, tôi thấy điều này rất khó giải thích rằng dự phòng đã được giới thiệu để tiết kiệm nhân viên hỗ trợ từ người dùng nhầm lẫn".

Tham chiếu: Stroustrup, Bjarne (1994). Thiết kế và sự tiến hóa của C++. Addison-Wesley.

0

Một cách khác để nhìn vào này ...

Với tất cả những vấn đề bảo trì đã được tạo ra bởi “nếu”, “trong khi”, “cho” và tuyên bố “foreach” không có căn cứ, rất nhiều công ty đã mã hóa các tiêu chuẩn luôn yêu cầu căn cứ vào các tuyên bố hoạt động trên một “khối”.

Vì vậy, họ làm cho bạn viết:

if (itIsSo) 
{ 
ASingleLineOfCode(); 
} 

Thay sau đó:

if (itIsSo) 
ASingleLineOfCode(); 

(Lưu ý là thụt không được chọn bởi trình biên dịch, nó không thể được phụ thuộc vào là đúng)

Một trường hợp tốt có thể được thực hiện để thiết kế một ngôn ngữ luôn đòi hỏi các căn cứ, nhưng sau đó quá nhiều người sẽ ghét C# do phải luôn luôn sử dụng các căn cứ. Tuy nhiên để thử/nắm bắt không có một kỳ vọng của việc có thể nhận được đi mà không sử dụng các căn cứ, do đó, nó đã có thể yêu cầu họ mà không có nhiều người phàn nàn.


Cho một lựa chọn, tôi sẽ có nhiều nếu/endIf (và while/endWhile) làm khối phân cách nhưng Hoa Kỳ đã đi theo cách đó. (C có để xác định những gì hầu hết ngôn ngữ giống như hơn module2, rốt cuộc cả hầu hết những gì chúng tôi được xác định bởi lịch sử không logic)