Tại sao là tuyên bố (j ++); Cấm?


169

Đoạn mã sau là sai (xem nó trên ideone ):

public class Test
{
    public static void Main()
    {
        int j = 5;
        (j++);      // if we remove the "(" and ")" then this compiles fine.
    }
}

lỗi CS0201: Chỉ có thể sử dụng phép gán, gọi, tăng, giảm, chờ và các biểu thức đối tượng mới làm câu lệnh

  1. Tại sao mã biên dịch khi chúng ta loại bỏ dấu ngoặc đơn?
  2. Tại sao nó không biên dịch với dấu ngoặc đơn?
  3. Tại sao C # được thiết kế theo cách đó?

29
@Servy rất nhiều người liên quan đến thiết kế ngôn ngữ C # đang ở trên SO, vì vậy đó là lý do tại sao tôi hỏi. Bất cứ điều gì sai với điều này?
dùng10607

34
Tôi không thể tưởng tượng một câu hỏi về chủ đề nhiều hơn "tại sao một ngôn ngữ lập trình cụ thể xử lý hành vi cụ thể này theo cách này?"
sư Xy

20
Bây giờ chúng tôi có một câu trả lời của Eric Lippert. Có lẽ bạn muốn suy nghĩ lại quyết định của bạn về câu trả lời được chấp nhận. Đó là Giáng sinh, nên có lẽ bạn đã chấp nhận câu trả lời quá sớm.
Thomas Weller

17
@Servy Bạn có ngụ ý rằng các quyết định thiết kế không bao giờ được ghi nhận và hoàn toàn không được biết đến với mọi người khác? Anh ấy không yêu cầu người dùng SO đoán, anh ấy đang hỏi câu trả lời - điều đó không có nghĩa là anh ấy đang hỏi để đoán. Cho dù mọi người trả lời với một phỏng đoán là ở họ , không phải anh ta. Và như OP đã chỉ ra, có những người trong stack stack đã thực sự làm việc trên C # và đưa ra những quyết định này.
Cướp

5
@Rob: Vấn đề là, vâng, trừ khi lý do đã được ghi nhận ở đâu đó, thì phải, nhưng suy đoán là thú vị và ai không muốn chia sẻ? Nếu bạn tìm ra cách để đảm bảo những phỏng đoán thuần túy (hoặc "có học thức") như vậy được loại bỏ một cách đáng tin cậy, hãy nói với tôi và chúng ta sẽ kiếm được tiền.
Ded repeatator

Câu trả lời:


221

Hiểu biết sâu sắc đánh giá cao.

Tôi sẽ làm hết sức mình.

Như các câu trả lời khác đã lưu ý, những gì đang diễn ra ở đây là trình biên dịch đang phát hiện ra rằng một biểu thức đang được sử dụng như một câu lệnh . Trong nhiều ngôn ngữ - C, JavaScript và nhiều ngôn ngữ khác - việc sử dụng một biểu thức làm tuyên bố là hoàn toàn hợp pháp. 2 + 2;là hợp pháp trong các ngôn ngữ này, mặc dù đây là một tuyên bố không có hiệu lực. Một số biểu thức chỉ hữu ích cho các giá trị của chúng, một số biểu thức chỉ hữu ích cho các tác dụng phụ của chúng (chẳng hạn như gọi đến phương thức trả về khoảng trống) và một số biểu thức, không may, hữu ích cho cả hai. (Giống như gia tăng.)

Điểm hiện hữu: các câu lệnh chỉ bao gồm các biểu thức gần như chắc chắn là các lỗi trừ khi các biểu thức đó thường được cho là hữu ích hơn cho các tác dụng phụ của chúng so với các giá trị của chúng . Các nhà thiết kế C # muốn tìm một nền tảng trung gian, bằng cách cho phép các biểu thức thường được coi là có tác dụng phụ, trong khi không cho phép các biểu thức thường được coi là hữu ích cho các giá trị của chúng. Tập hợp các biểu thức mà chúng đã xác định trong C # 1.0 là các số gia, số giảm, lệnh gọi phương thức, bài tập và một số tranh cãi, các cách gọi của hàm tạo.


ASIDE: Người ta thường nghĩ về việc xây dựng đối tượng là được sử dụng cho giá trị mà nó tạo ra, không phải cho tác dụng phụ của việc xây dựng; theo ý kiến ​​của tôi cho phép new Foo();là một chút sai lầm. Cụ thể, tôi đã thấy mô hình này trong mã trong thế giới thực gây ra lỗi bảo mật:

catch(FooException ex) { new BarException(ex); } 

Thật khó có thể ngạc nhiên khi phát hiện ra khiếm khuyết này nếu mã phức tạp.


Do đó trình biên dịch hoạt động để phát hiện tất cả các câu lệnh bao gồm các biểu thức không có trong danh sách đó. Cụ thể, các biểu thức được ngoặc đơn được xác định là như vậy - các biểu thức được ngoặc đơn. Chúng không nằm trong danh sách "được phép dưới dạng biểu thức câu lệnh", vì vậy chúng không được phép.

Tất cả điều này là để phục vụ cho một nguyên tắc thiết kế của ngôn ngữ C #. Nếu bạn gõ (x++);bạn có thể đã làm điều gì đó sai . Đây có lẽ là một lỗi đánh máy cho M(x++);hoặc một số điều. Hãy nhớ rằng, thái độ của nhóm trình biên dịch C # không phải là " chúng ta có thể tìm ra cách nào đó để thực hiện công việc này không? " Thái độ của nhóm trình biên dịch C # là " nếu mã hợp lý có vẻ như là một lỗi có thể xảy ra, hãy thông báo cho nhà phát triển ". Các nhà phát triển C # thích thái độ đó.

Bây giờ, tất cả những gì đã nói, có thực sự là một vài trường hợp lẻ nơi # đặc điểm kỹ thuật C không ngụ ý hoặc trạng thái hoàn toàn rằng ngoặc là không được phép nhưng biên dịch C # cho phép chúng anyways. Trong hầu hết các trường hợp, sự khác biệt nhỏ giữa hành vi được chỉ định và hành vi được phép là hoàn toàn vô hại, vì vậy người viết trình biên dịch chưa bao giờ sửa các lỗi nhỏ này. Bạn có thể đọc về những người ở đây:

Có sự khác biệt giữa return myVar so với return (myVar) không?


5
Re: "Người ta thường nghĩ rằng một lời gọi của nhà xây dựng là được sử dụng cho giá trị mà nó tạo ra, không phải cho tác dụng phụ của việc xây dựng; theo tôi đây là một chút sai lầm": Tôi tưởng tượng rằng một yếu tố trong quyết định này là nếu trình biên dịch cấm nó, sẽ không có bất kỳ sửa chữa sạch trong trường hợp bạn đang gọi đó là một tác dụng phụ. (Hầu hết các câu lệnh biểu thức bị cấm khác có các phần không liên quan không phục vụ mục đích và có thể được gỡ bỏ, ngoại trừ ... ? ... : ..., trong đó cách khắc phục là sử dụng if/ elsethay vào đó.)
ruakh

1
@ruakh: Vì đây là hành vi nên được khuyến khích, nên không cần sửa chữa sạch sẽ, miễn là có một sửa chữa hợp lý / rẻ tiền hợp lý. Mà có; gán nó cho một biến và không sử dụng biến đó. Nếu bạn muốn trắng trợn rằng bạn không sử dụng nó, hãy cung cấp dòng mã đó là phạm vi riêng. Mặc dù về mặt kỹ thuật, người ta có thể lập luận rằng điều này thay đổi ngữ nghĩa của mã (một phép gán tham chiếu vô dụng vẫn là một phép gán tham chiếu vô dụng), trong thực tế, điều đó khó có thể xảy ra. Tôi thừa nhận rằng nó tạo ra IL hơi khác nhau ... nhưng chỉ khi được biên dịch mà không tối ưu hóa.
Brian

Safari, Firefox và Chrome đều cung cấp ReferenceError khi nhập contineu;.
Brian McCutchon

2
@McBrainy: Bạn nói đúng. Tôi đang đánh giá sai về lỗi phát sinh từ lỗi chính tả; Tôi sẽ kiểm tra ghi chú của tôi. Trong khi đó, tôi đã xóa tuyên bố vi phạm vì nó không phải là nguyên nhân đến điểm lớn hơn ở đây.
Eric Lippert

1
@Mike: Tôi đồng ý. Một tình huống khác trong đó đôi khi chúng ta thấy tuyên bố là các trường hợp thử nghiệm try { new Foo(null); } catch (ArgumentNullException)...nhưng rõ ràng những tình huống này theo định nghĩa không phải là mã sản xuất. Có vẻ hợp lý rằng mã như vậy có thể được viết để gán cho một biến giả.
Eric Lippert

46

Trong đặc tả ngôn ngữ C #

Các biểu thức được sử dụng để đánh giá các biểu thức. Các biểu thức có thể được sử dụng như các câu lệnh bao gồm các dẫn xuất phương thức, phân bổ đối tượng bằng toán tử mới, các phép gán sử dụng = và các toán tử gán gán tổng hợp, các phép toán tăng và giảm sử dụng toán tử ++ và - và chờ các biểu thức.

Đặt dấu ngoặc đơn xung quanh một câu lệnh sẽ tạo ra một biểu thức được gọi là dấu ngoặc đơn mới. Từ đặc điểm kỹ thuật:

Biểu thức ngoặc đơn bao gồm một biểu thức được đặt trong dấu ngoặc đơn. ... Một biểu thức ngoặc đơn được đánh giá bằng cách đánh giá biểu thức trong ngoặc đơn. Nếu biểu thức trong ngoặc đơn biểu thị một không gian tên hoặc kiểu, lỗi thời gian biên dịch xảy ra. Mặt khác, kết quả của biểu thức được ngoặc đơn là kết quả của việc đánh giá biểu thức được chứa.

Vì các biểu thức được ngoặc đơn không được liệt kê dưới dạng một câu lệnh biểu thức hợp lệ, nó không phải là một câu lệnh hợp lệ theo đặc tả. Tại sao các nhà thiết kế chọn làm theo cách này là dự đoán của bất kỳ ai, nhưng tôi cá là vì dấu ngoặc đơn không có tác dụng hữu ích nếu toàn bộ câu lệnh được chứa trong ngoặc đơn: stmt(stmt)hoàn toàn giống nhau.


4
@Servy: Nếu bạn đọc kỹ, nó có nhiều sắc thái hơn thế. Cái gọi là "Tuyên bố biểu thức" thực sự là một câu lệnh hợp lệ và đặc tả liệt kê các loại biểu thức có thể là câu lệnh hợp lệ. Tuy nhiên, tôi nhắc lại từ thông số kỹ thuật rằng loại biểu thức được gọi là "biểu thức được ngoặc đơn" KHÔNG có trong danh sách các biểu thức hợp lệ có thể được sử dụng làm câu lệnh biểu thức hợp lệ.
Frank Bryce

4
Không OPhỏi gì về thiết kế ngôn ngữ. Anh ta chỉ muốn biết tại sao đây là một lỗi. Câu trả lời là: bởi vì nó không phải là một tuyên bố hợp lệ .
Leandro

9
OK, xấu của tôi. Câu trả lời là: bởi vì, theo đặc điểm kỹ thuật , nó không phải là một tuyên bố hợp lệ. Có bạn có nó: một lý do thiết kế !
Leandro

3
Câu hỏi không phải là yêu cầu một lý do sâu xa, hướng đến thiết kế là tại sao ngôn ngữ được thiết kế để xử lý cú pháp này theo cách này - anh ta hỏi tại sao một đoạn mã biên dịch khi một đoạn mã khác (mà người mới bắt đầu trông giống như nó nên hành xử giống hệt) không biên dịch. Bài này trả lời rằng.
sư Xy

4
@Servy JohnCarpenter không chỉ nói "Bạn không thể làm điều đó." Anh ta nói, hai điều mà bạn bối rối không giống nhau. j ++ là một biểu thức biểu thức, trong khi (j ++) là một biểu thức được ngoặc đơn. Thật bất ngờ khi bây giờ OP biết sự khác biệt và chúng là gì. Đó là một câu trả lời tốt. Nó trả lời cơ thể câu hỏi của anh ấy không phải tiêu đề. Tôi nghĩ rằng rất nhiều sự tranh cãi xuất phát từ từ "thiết kế" trong tiêu đề của câu hỏi, nhưng điều này không phải được các nhà thiết kế trả lời, chỉ cần lượm lặt từ các thông số kỹ thuật.
thinklarge

19

vì các dấu ngoặc xung quanh i++đang tạo / xác định biểu thức .. như thông báo lỗi cho biết .. một biểu thức đơn giản không thể được sử dụng làm câu lệnh.

Tại sao ngôn ngữ được thiết kế theo cách này? để ngăn chặn lỗi, có các biểu thức gây hiểu lầm như các câu lệnh, không tạo ra tác dụng phụ như có mã

int j = 5;
j+1; 

dòng thứ hai không có hiệu lực (nhưng bạn có thể không nhận thấy). Nhưng thay vì trình biên dịch loại bỏ nó (vì không cần mã) .it rõ ràng yêu cầu bạn xóa nó (vì vậy bạn sẽ nhận thức được hoặc lỗi) HOẶC sửa nó trong trường hợp bạn quên gõ một cái gì đó.

chỉnh sửa :

để làm cho phần về nước lợ rõ ràng hơn .. dấu ngoặc trong c # (bên cạnh các cách sử dụng khác, như ép kiểu và gọi hàm), được sử dụng để nhóm các biểu thức và trả về một biểu thức (tạo các biểu thức phụ).

ở cấp mã đó chỉ cho phép các nhị hoa .. vì vậy

j++; is a valid statement because it produces side effects

nhưng bằng cách sử dụng nước lợ, bạn đang biến nó thành biểu thức

myTempExpression = (j++)

và điều này

myTempExpression;

không hợp lệ vì trình biên dịch không thể đảm bảo rằng biểu thức là tác dụng phụ. (không phải không phát sinh vấn đề tạm dừng) ..


Vâng, đó là những gì tôi nghĩ lúc đầu quá. Nhưng điều này không có tác dụng phụ. Nó thực hiện gia tăng bài của jbiến phải không?
dùng10607

1
j++;là một tuyên bố hợp lệ, nhưng bằng cách sử dụng dấu ngoặc mà bạn đang nói let me take this stement and turn it into an expression.. và các biểu hiện không phải là hợp lệ tại thời điểm đó trong mã
CaldasGSM

Thật tệ khi không có hình thức tuyên bố "tính toán và bỏ qua", vì có những lúc mã có thể muốn khẳng định rằng một giá trị có thể được tính mà không cần ném ngoại lệ, nhưng không quan tâm đến giá trị thực do đó được tính toán. Một câu lệnh tính toán và bỏ qua sẽ không được sử dụng thường xuyên, nhưng sẽ làm cho ý định của lập trình viên rõ ràng trong những trường hợp như vậy.
supercat
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.