Câu hỏi này là chủ đề của blog của tôi vào ngày 20 tháng 9 năm 2010 . Câu trả lời của Josh và Chad ("chúng không có giá trị gì thêm vậy tại sao lại yêu cầu chúng?" Và "để loại bỏ sự dư thừa") về cơ bản là đúng. Để làm rõ điều đó hơn một chút:
Tính năng cho phép bạn lướt qua danh sách đối số như một phần của "tính năng lớn hơn" của trình khởi tạo đối tượng đã đáp ứng thanh của chúng tôi cho các tính năng "có đường". Một số điểm chúng tôi đã xem xét:
- chi phí thiết kế và đặc điểm kỹ thuật thấp
- chúng tôi sẽ thay đổi rộng rãi mã phân tích cú pháp xử lý việc tạo đối tượng; chi phí phát triển bổ sung của việc làm cho danh sách tham số tùy chọn không lớn so với chi phí của tính năng lớn hơn
- gánh nặng thử nghiệm tương đối nhỏ so với chi phí của tính năng lớn hơn
- gánh nặng tài liệu tương đối nhỏ so với ...
- gánh nặng bảo trì được dự đoán là nhỏ; Tôi không nhớ bất kỳ lỗi nào được báo cáo trong tính năng này trong những năm kể từ khi nó xuất xưởng.
- tính năng này không gây ra bất kỳ rủi ro rõ ràng nào ngay lập tức cho các tính năng trong tương lai trong lĩnh vực này. (Điều cuối cùng chúng tôi muốn làm là tạo ra một tính năng rẻ, dễ dàng ngay bây giờ, điều này khiến việc triển khai một tính năng hấp dẫn hơn trong tương lai sẽ khó hơn nhiều).
- tính năng này không bổ sung thêm sự mơ hồ mới vào phân tích từ vựng, ngữ pháp hoặc ngữ nghĩa của ngôn ngữ. Nó không gây ra vấn đề gì cho loại phân tích "chương trình một phần" được thực hiện bởi công cụ "IntelliSense" của IDE trong khi bạn đang nhập. Và như thế.
- tính năng đạt được một "điểm ngọt" chung cho tính năng khởi tạo đối tượng lớn hơn; thường nếu bạn đang sử dụng một bộ khởi tạo đối tượng thì đó chính là vì hàm tạo của đối tượng không cho phép bạn thiết lập các thuộc tính mà bạn muốn. Rất phổ biến đối với những đối tượng như vậy chỉ đơn giản là "túi tài sản" mà không có tham số trong ctor ngay từ đầu.
Tại sao sau đó bạn không đặt dấu ngoặc đơn trống tùy chọn trong lệnh gọi hàm tạo mặc định của một biểu thức tạo đối tượng không có bộ khởi tạo đối tượng?
Hãy nhìn vào danh sách các tiêu chí ở trên. Một trong số đó là sự thay đổi không tạo ra bất kỳ sự mơ hồ mới nào trong phân tích từ vựng, ngữ pháp hoặc ngữ nghĩa của một chương trình. Thay đổi được đề xuất của bạn không tạo ra sự mơ hồ về phân tích ngữ nghĩa:
class P
{
class B
{
public class M { }
}
class C : B
{
new public void M(){}
}
static void Main()
{
new C().M(); // 1
new C.M(); // 2
}
}
Dòng 1 tạo một C mới, gọi hàm tạo mặc định, sau đó gọi phương thức thể hiện M trên đối tượng mới. Dòng 2 tạo một thể hiện mới của BM và gọi hàm tạo mặc định của nó. Nếu dấu ngoặc đơn trên dòng 1 là tùy chọn thì dòng 2 sẽ không rõ ràng. Sau đó, chúng tôi sẽ phải đưa ra một quy tắc giải quyết sự mơ hồ; chúng tôi không thể tạo thành lỗi vì đó sẽ là một thay đổi đột ngột làm thay đổi chương trình C # hợp pháp hiện có thành một chương trình bị hỏng.
Do đó, quy tắc sẽ phải rất phức tạp: về cơ bản, dấu ngoặc đơn chỉ là tùy chọn trong trường hợp chúng không tạo ra sự mơ hồ. Chúng tôi sẽ phải phân tích tất cả các trường hợp có thể gây ra sự mơ hồ và sau đó viết mã trong trình biên dịch để phát hiện chúng.
Trong ánh sáng đó, hãy quay lại và xem xét tất cả các chi phí mà tôi đề cập. Bao nhiêu trong số chúng bây giờ trở nên lớn? Các quy tắc phức tạp có chi phí thiết kế, thông số kỹ thuật, phát triển, thử nghiệm và tài liệu lớn. Các quy tắc phức tạp có nhiều khả năng gây ra sự cố với các tương tác không mong muốn với các tính năng trong tương lai.
Tất cả để làm gì? Một lợi ích khách hàng nhỏ không bổ sung sức mạnh đại diện mới cho ngôn ngữ, nhưng lại thêm vào những trường hợp ngớ ngẩn điên cuồng chỉ chờ hét lên "gotcha" với một linh hồn tội nghiệp không nghi ngờ nào đó chạy vào nó. Các tính năng như vậy sẽ bị cắt ngay lập tức và đưa vào danh sách "không bao giờ làm điều này".
Làm thế nào bạn xác định được sự mơ hồ cụ thể đó?
Điều đó ngay lập tức rõ ràng; Tôi khá quen thuộc với các quy tắc trong C # để xác định khi nào một tên có dấu chấm được mong đợi.
Khi xem xét một tính năng mới, làm thế nào để bạn xác định xem nó có gây ra bất kỳ sự mơ hồ nào không? Bằng tay, bằng chứng chính thức, bằng phân tích máy móc, cái gì?
Cả ba. Hầu hết chúng ta chỉ nhìn vào thông số kỹ thuật và mì trên đó, như tôi đã làm ở trên. Ví dụ: giả sử chúng ta muốn thêm một toán tử tiền tố mới vào C # được gọi là "frob":
x = frob 123 + 456;
(CẬP NHẬT: frob
là tất nhiên await
; phân tích ở đây về cơ bản là phân tích mà nhóm thiết kế đã trải qua khi thêm vào await
.)
"frob" ở đây giống như "new" hoặc "++" - nó đứng trước một biểu thức của một số loại. Chúng tôi sẽ tìm ra mức độ ưu tiên và tính liên kết mong muốn, v.v. và sau đó bắt đầu đặt các câu hỏi như "điều gì sẽ xảy ra nếu chương trình đã có một kiểu, trường, thuộc tính, sự kiện, phương thức, hằng số hoặc cục bộ được gọi là frob?" Điều đó sẽ ngay lập tức dẫn đến các trường hợp như:
frob x = 10;
điều đó có nghĩa là "thực hiện phép toán Frob dựa trên kết quả của x = 10 hay tạo một biến kiểu frob được gọi là x và gán 10 cho nó?" (Hoặc, nếu việc đóng băng tạo ra một biến, nó có thể là một phép gán 10 cho frob x
. Rốt cuộc, *x = 10;
phân tích cú pháp và hợp pháp nếu x
có int*
.)
G(frob + x)
Điều đó có nghĩa là "giá trị kết quả của toán tử cộng một bậc trên x" hoặc "thêm biểu thức giá trị vào x"?
Và như thế. Để giải quyết những sự mơ hồ này, chúng tôi có thể giới thiệu phương pháp heuristics. Khi bạn nói "var x = 10;" điều đó thật mơ hồ; nó có thể có nghĩa là "suy ra loại x" hoặc nó có thể có nghĩa là "x thuộc loại var". Vì vậy, chúng tôi có một heuristic: trước tiên chúng tôi cố gắng tìm kiếm một kiểu có tên var, và chỉ khi một kiểu không tồn tại, chúng tôi mới suy ra kiểu của x.
Hoặc, chúng tôi có thể thay đổi cú pháp để nó không mơ hồ. Khi họ thiết kế C # 2.0, họ đã gặp vấn đề này:
yield(x);
Điều đó có nghĩa là "lợi nhuận x trong một trình lặp" hoặc "gọi phương thức lợi nhuận với đối số x?" Bằng cách thay đổi nó thành
yield return(x);
nó bây giờ là rõ ràng.
Trong trường hợp các parens tùy chọn trong bộ khởi tạo đối tượng, có thể dễ dàng lập luận về việc liệu có sự mơ hồ nào được đưa vào hay không bởi vì số lượng tình huống được phép giới thiệu thứ gì đó bắt đầu bằng {là rất nhỏ . Về cơ bản chỉ là các ngữ cảnh câu lệnh khác nhau, lambdas câu lệnh, trình khởi tạo mảng và đó là về nó. Thật dễ dàng để suy luận qua tất cả các trường hợp và cho thấy rằng không có sự mơ hồ. Đảm bảo IDE vẫn hoạt động hiệu quả có phần khó hơn nhưng có thể được thực hiện mà không gặp quá nhiều khó khăn.
Loại này thường là đủ. Nếu đó là một tính năng đặc biệt phức tạp thì chúng tôi lấy ra các công cụ nặng hơn. Ví dụ: khi thiết kế LINQ, một trong những người biên dịch và một trong những người IDE, cả hai đều có nền tảng về lý thuyết phân tích cú pháp đã tự xây dựng cho mình một trình tạo trình phân tích cú pháp có thể phân tích ngữ pháp tìm kiếm sự mơ hồ và sau đó cung cấp ngữ pháp C # được đề xuất để hiểu truy vấn vào đó ; làm như vậy đã tìm thấy nhiều trường hợp truy vấn không rõ ràng.
Hoặc, khi chúng tôi thực hiện suy luận kiểu nâng cao trên lambdas trong C # 3.0, chúng tôi đã viết các đề xuất của mình và sau đó gửi chúng qua ao đến Microsoft Research ở Cambridge, nơi nhóm ngôn ngữ ở đó đủ giỏi để tạo ra một bằng chứng chính thức rằng đề xuất kiểu suy luận là về mặt lý thuyết là âm thanh.
Có sự mơ hồ nào trong C # ngày nay không?
Chắc chắn rồi.
G(F<A, B>(0))
Trong C # 1, rõ ràng điều đó có nghĩa là gì. Nó giống như:
G( (F<A), (B>0) )
Tức là nó gọi G với hai đối số là bools. Trong C # 2, điều đó có thể có nghĩa là trong C # 1, nhưng nó cũng có thể có nghĩa là "chuyển 0 cho phương thức chung F nhận các tham số kiểu A và B, sau đó chuyển kết quả của F cho G". Chúng tôi đã thêm một heuristic phức tạp vào trình phân tích cú pháp để xác định trường hợp nào trong hai trường hợp mà bạn có thể muốn nói.
Tương tự, các phôi là không rõ ràng ngay cả trong C # 1.0:
G((T)-x)
Đó là "ép -x thành T" hay "trừ x khỏi T"? Một lần nữa, chúng tôi có một phương pháp phỏng đoán giúp đưa ra một dự đoán chính xác.