Tất cả các quy trình thiết kế dẫn đến sự thỏa hiệp giữa các mục tiêu không tương thích lẫn nhau. Thật không may, quá trình thiết kế cho &&
toán tử quá tải trong C ++ đã tạo ra một kết quả cuối cùng khó hiểu: rằng chính tính năng bạn muốn &&
- hành vi ngắn mạch của nó - bị bỏ qua.
Các chi tiết về cách quá trình thiết kế đó kết thúc ở nơi không may này, những người tôi không biết. Tuy nhiên, có liên quan để xem làm thế nào một quá trình thiết kế sau này đã tính đến kết quả khó chịu này. Trong C #, &&
toán tử quá tải là ngắn mạch. Làm thế nào mà các nhà thiết kế của C # đạt được điều đó?
Một trong những câu trả lời khác cho thấy "nâng lambda". Đó là:
A && B
có thể được nhận ra như một cái gì đó tương đương về mặt đạo đức với:
operator_&& ( A, ()=> B )
trong đó đối số thứ hai sử dụng một số cơ chế để đánh giá lười biếng để khi được đánh giá, các tác dụng phụ và giá trị của biểu thức được tạo ra. Việc thực hiện toán tử quá tải sẽ chỉ thực hiện đánh giá lười biếng khi cần thiết.
Đây không phải là những gì nhóm thiết kế C # đã làm. (Ngoài ra: mặc dù việc nâng lambda là những gì tôi đã làm khi đến lúc phải thực hiện biểu diễn cây biểu thức của ??
toán tử, đòi hỏi một số thao tác chuyển đổi phải được thực hiện một cách lười biếng. Mô tả chi tiết tuy nhiên sẽ là một sự cải tiến lớn. hoạt động nhưng đủ nặng mà chúng tôi muốn tránh nó.)
Thay vào đó, giải pháp C # chia vấn đề thành hai vấn đề riêng biệt:
- chúng ta nên đánh giá toán hạng bên tay phải?
- Nếu câu trả lời ở trên là "có", thì làm thế nào để chúng ta kết hợp hai toán hạng?
Do đó, vấn đề được giải quyết bằng cách làm cho nó quá tải &&
trực tiếp. Thay vào đó, trong C #, bạn phải quá tải hai toán tử, mỗi toán tử trả lời một trong hai câu hỏi đó.
class C
{
// Is this thing "false-ish"? If yes, we can skip computing the right
// hand size of an &&
public static bool operator false (C c) { whatever }
// If we didn't skip the RHS, how do we combine them?
public static C operator & (C left, C right) { whatever }
...
(Ngoài ra: trên thực tế, ba. C # yêu cầu rằng nếu toán tử false
được cung cấp thì toán tử true
cũng phải được cung cấp, câu trả lời cho câu hỏi: đây có phải là "true-ish?". Thông thường sẽ không có lý do gì để chỉ cung cấp một toán tử như vậy nên C # yêu cầu cả hai.)
Xem xét một tuyên bố của mẫu:
C cresult = cleft && cright;
Trình biên dịch tạo mã cho điều này như bạn nghĩ rằng bạn đã viết giả này C #:
C cresult;
C tempLeft = cleft;
cresult = C.false(tempLeft) ? tempLeft : C.&(tempLeft, cright);
Như bạn có thể thấy, phía bên tay trái luôn được đánh giá. Nếu nó được xác định là "false-ish" thì đó là kết quả. Mặt khác, phía bên tay phải được ước tính và toán tử do người dùng định nghĩa háo hức&
được gọi.
Các ||
nhà điều hành được xác định theo cách tương tự, như một lời khẩn cầu của nhà điều hành trung thực và sự háo hức |
điều hành:
cresult = C.true(tempLeft) ? tempLeft : C.|(tempLeft , cright);
Bằng việc xác định tất cả bốn nhà khai thác - true
, false
, &
và |
- C # cho phép bạn không chỉ nói cleft && cright
mà còn không chập mạch cleft & cright
, và cũng có thể if (cleft) if (cright) ...
, và c ? consequence : alternative
và while(c)
, và vân vân.
Bây giờ, tôi đã nói rằng tất cả các quy trình thiết kế là kết quả của sự thỏa hiệp. Ở đây, các nhà thiết kế ngôn ngữ C # đã quản lý để có được ngắn mạch &&
và ||
đúng, nhưng làm như vậy đòi hỏi quá tải bốn toán tử thay vì hai , điều mà một số người cảm thấy khó hiểu. Tính năng đúng / sai của toán tử là một trong những tính năng ít được hiểu nhất trong C #. Mục tiêu có một ngôn ngữ hợp lý và đơn giản, quen thuộc với người dùng C ++ đã bị phản đối bởi mong muốn được đoản mạch và mong muốn không thực hiện nâng lambda hoặc các hình thức đánh giá lười biếng khác. Tôi nghĩ rằng đó là một vị trí thỏa hiệp hợp lý, nhưng điều quan trọng là phải nhận ra rằng đó là một vị trí thỏa hiệp. Chỉ là khác thỏa hiệp vị trí hơn các nhà thiết kế của C ++ hạ cánh.
Nếu chủ đề thiết kế ngôn ngữ cho các toán tử đó làm bạn quan tâm, hãy xem xét việc đọc loạt bài của tôi về lý do tại sao C # không định nghĩa các toán tử này trên Booleans nullable:
http://ericlippert.com/2012/03/26/null-is-not-false-part-one/
operator&&(const Foo& lhs, const Foo& rhs) : (lhs.bars == 0)