2011-02-04 35 views
48

Như bạn đã biết, nó không được phép sử dụng cú pháp-Array-initialisation với Danh sách. Nó sẽ cung cấp cho một lỗi thời gian biên dịch. Ví dụ:Danh sách <int> kiểm tra = {1, 2, 3} - đó có phải là một tính năng hoặc lỗi không?

List<int> test = { 1, 2, 3} 
// At compilation the following error is shown: 
// Can only use array initializer expressions to assign to array types. 

Tuy nhiên hôm nay tôi đã làm như sau (rất đơn giản):

class Test 
{ 
    public List<int> Field; 
} 

List<Test> list = new List<Test> 
{ 
    new Test { Field = { 1, 2, 3 } } 
}; 

Đoạn mã trên biên dịch tốt, nhưng khi chạy nó sẽ cho một "đối tượng tài liệu tham khảo không được đặt để một đối tượng " lỗi runtime.

Tôi hy vọng mã đó sẽ cung cấp lỗi biên dịch. Câu hỏi của tôi đối với bạn là: Tại sao không, và có lý do chính đáng nào để khi một kịch bản như vậy chạy đúng không?

Điều này đã được thử nghiệm bằng cách sử dụng .NET 3.5, cả trình biên dịch .Net và Mono.

Chúc mừng.

+1

Ví dụ đơn giản 'Kiểm tra thử nghiệm = new Test {Field = {1, 2, 3}};' có cùng hành vi. – CodesInChaos

+3

"Tham chiếu đối tượng không được đặt thành đối tượng" là khá hợp lý vì trường danh sách của bạn không được khởi tạo. Thay đổi nội dung Kiểm tra thành Danh sách công khai Trường = Danh sách mới (); làm cho điều này chạy mà không có bất kỳ vấn đề cũng có. –

+0

có một phản xạ, và xem cách trình biên dịch xử lý này, và bạn sẽ hiểu. cũng xem cách var stuff = new List () {4,5}; được handeled –

Trả lời

43

Tôi nghĩ đây là hành vi theo thiết kế. Test = { 1, 2, 3 } được biên dịch thành mã gọi phương thức Add của danh sách được lưu trữ trong trường Test.

Lý do bạn nhận được NullReferenceExceptionTestnull. Nếu bạn khởi tạo lĩnh vực Test vào một danh sách mới, sau đó mã sẽ làm việc:

class Test {  
    public List<int> Field = new List<int>(); 
} 

// Calls 'Add' method three times to add items to 'Field' list 
var t = new Test { Field = { 1, 2, 3 } }; 

Nó là khá hợp lý - nếu bạn viết new List<int> { ... } sau đó nó tạo ra một thể hiện mới của danh sách. Nếu bạn không thêm xây dựng đối tượng, nó sẽ sử dụng cá thể hiện có (hoặc null). Theo như tôi thấy, C# spec không chứa bất kỳ quy tắc dịch rõ ràng rằng sẽ phù hợp với tình huống này, nhưng nó mang lại một ví dụ (xem Mục 7.6.10.3):

Một List<Contact> có thể được tạo ra và khởi tạo như sau:

var contacts = new List<Contact> { 
    new Contact { 
     Name = "Chris Smith", 
     PhoneNumbers = { "206-555-0101", "425-882-8080" } 
    }, 
    new Contact { 
     Name = "Bob Harris", 
     PhoneNumbers = { "650-555-0199" } 
    } 
}; 

trong đó có tác dụng tương tự như

var contacts = new List<Contact>(); 
Contact __c1 = new Contact(); 
__c1.Name = "Chris Smith"; 
__c1.PhoneNumbers.Add("206-555-0101"); 
__c1.PhoneNumbers.Add("425-882-8080"); 
contacts.Add(__c1); 
Contact __c2 = new Contact(); 
__c2.Name = "Bob Harris"; 
__c2.PhoneNumbers.Add("650-555-0199"); 
contacts.Add(__c2); 

trong đó __c1__c2 là các biến tạm thời không nhìn thấy được và không thể truy cập được.

+0

Một chút có liên quan: http://stackoverflow.com/questions/459652/why-do-c-collection-initializers-work-this-way –

+0

Cảm ơn bạn, câu trả lời tuyệt vời. Với mô tả này, hành vi là hợp lý. Tuy nhiên những gì vẫn không hợp lý với tôi là cho phép = {} cú pháp để thêm các mục, theo cách này tôi sẽ đọc "Trường này bằng", nhưng trong thực tế, danh sách có thể chứa nhiều mục hơn các mục tôi đã chỉ định nếu có khi xây dựng đối tượng. Tôi thà có một lỗi biên dịch ;-) – Mika

+1

Có, có một chút mâu thuẫn. Sử dụng logic từ mẫu này, người ta có thể viết 'var l = new List (); l = {1, 2, 3}; '- nhưng điều đó không hiệu quả! –

3
var test = (new [] { 1, 2, 3}).ToList(); 
+5

Anh ấy không hỏi làm thế nào để làm cho nó hoạt động, anh ấy hỏi tại sao chương trình gốc thậm chí biên dịch. –

+5

nó biên dịch bởi vì nó là hợp lệ C#, nó không thành công tại thời gian chạy vì ông đã không nhanh chóng đối tượng danh sách –

+0

@K Ivanov: Thật là một lời giải thích tuyệt vời. – BoltClock

1

Thay đổi mã của bạn như thế này:

class Test 
{ 
    public List<int> Field = new List<int>(); 
} 

Lý do là bạn phải tạo một cách rõ ràng đối tượng bộ sưu tập trước khi bạn có thể đặt các mục vào nó.

+3

Anh ấy không hỏi làm thế nào để làm cho nó hoạt động, anh ấy hỏi tại sao chương trình gốc thậm chí biên dịch. –

+0

Nó biên dịch vì nó là chính xác. Bạn muốn nghe gì hơn? –

+1

Bạn có thể giải thích tại sao mã của OP biên dịch lại. – CodesInChaos

18

Mã này:

Test t = new Test { Field = { 1, 2, 3 } }; 

được phiên dịch sang này:

Test t = new Test(); 
t.Field.Add(1); 
t.Field.Add(2); 
t.Field.Add(3); 

Kể từ Fieldnull, bạn sẽ có được NullReferenceException.

này được gọi là collection initializer, và nó sẽ làm việc trong ví dụ ban đầu của bạn nếu bạn làm điều này:

List<int> test = new List<int> { 1, 2, 3 }; 

Bạn thực sự cần phải mới lên một cái gì đó để có thể sử dụng cú pháp này, ví dụ, một bộ khởi tạo bộ sưu tập chỉ có thể xuất hiện trong ngữ cảnh của một biểu thức tạo đối tượng. Trong thông số C#, phần 7.6.10.1, đây là cú pháp cho một biểu thức tạo đối tượng:

Vì vậy, tất cả bắt đầu với biểu thức new. Bên trong biểu thức, bạn có thể sử dụng một initializer bộ sưu tập mà không có sự new (phần 7.6.10.2):

object-initializer: 
    { member-initializer-list? } 
    { member-initializer-list , } 
member-initializer-list: 
    member-initializer 
    member-initializer-list , member-initializer 
member-initializer: 
    identifier = initializer-value 
initializer-value: 
    expression 
    object-or-collection-initializer // here it recurses 

Bây giờ, những gì bạn đang thực sự thiếu một số loại danh sách đen, đó sẽ là thực sự tiện dụng. Tôi đã đề xuất một chữ như vậy cho số đếm here.

+0

Vâng, chính xác. Các mã ban đầu là cú pháp chính xác nhưng thực sự không có danh sách để đưa những mục vào. –

+0

Ví dụ cuối cùng của bạn nổi tiếng. Nhưng tôi không biết bạn có thể sử dụng bộ khởi tạo bộ sưu tập bên trong bộ khởi tạo đối tượng mà không có 'MyCollection' mới. Và bài viết MSDN bạn đã liên kết cũng không đề cập đến điều đó. – CodesInChaos

+0

Một câu trả lời hay khác. Tôi đoán họ phải làm theo cách này để cho phép thêm các mục mà không ghi đè các giá trị có thể đã được thêm vào trong hàm tạo của đối tượng ban đầu. Đó là một chút mơ hồ vì bạn mong đợi giá trị kết quả của một hoạt động '=' là một kết hợp chính xác của việc gán bên phải, nhưng trong trường hợp này, nó chỉ thêm một tập các giá trị. – Mika

2

Lý do cho điều này là ví dụ thứ hai là danh sách thành viên initialiser - và sự biểu hiện MemberListBinding từ System.Linq.Expressions đưa ra một cái nhìn sâu sắc này - vui lòng xem câu trả lời của tôi cho câu hỏi này khác để xem chi tiết hơn: What are some examples of MemberBinding LINQ expressions?

Loại trình khởi tạo này yêu cầu danh sách đã được khởi tạo, do đó chuỗi bạn cung cấp có thể được thêm vào nó.

Kết quả là - cú pháp hoàn toàn không có gì sai với mã - NullReferenceException là lỗi thời gian chạy do Danh sách không thực sự được tạo. Một hàm tạo mặc định có danh sách new s hoặc nội tuyến mã new, sẽ giải quyết lỗi thời gian chạy.

Vì lý do tại sao có sự khác biệt giữa mã đó và dòng mã đầu tiên - trong ví dụ của bạn không được phép vì loại biểu thức này không thể ở phía bên phải của bài tập vì không thực sự tạo mọi thứ, nó chỉ viết tắt cho Add.

+0

Tôi đã xem liên kết. Những thứ tốt để có thêm kiến ​​thức chuyên sâu về cách mọi thứ được thực hiện. Cảm ơn! Đối với ví dụ của tôi, tôi nghĩ rằng nó rất rõ ràng bây giờ với tất cả các câu trả lời tôi đã nhận :) – Mika

25

Tôi hy vọng mã đó sẽ cung cấp lỗi biên dịch.

Vì kỳ vọng của bạn trái với cả đặc điểm kỹ thuật và triển khai, kỳ vọng của bạn sẽ không được thực hiện.

Tại sao nó không thành công trong thời gian biên dịch?

Do đặc điểm kỹ thuật nêu rõ điều đó là hợp pháp trong phần 7.6.10.2, mà tôi trích dẫn ở đây thuận tiện cho bạn:


Một initializer thành viên đó quy định cụ thể một initializer bộ sưu tập sau dấu bằng là một khởi của một bộ sưu tập nhúng. Thay vì gán một bộ sưu tập mới cho trường hoặc thuộc tính, các phần tử được đưa ra trong bộ khởi tạo được thêm vào bộ sưu tập được tham chiếu bởi trường hoặc thuộc tính.


khi sẽ như đang chạy một cách chính xác?

Khi thông số cho biết, các yếu tố được đưa ra trong bộ khởi tạo được thêm vào bộ sưu tập được tham chiếu bởi thuộc tính. Chỗ nghỉ không tham chiếu đến bộ sưu tập; nó là null. Do đó trong thời gian chạy nó cho một ngoại lệ tham chiếu null. Ai đó phải khởi tạo danh sách. Tôi khuyên bạn nên thay đổi lớp "Test" để hàm khởi tạo của nó khởi tạo danh sách.

Kịch bản nào thúc đẩy tính năng này?

Truy vấn LINQ cần biểu thức, chứ không phải câu lệnh. Thêm thành viên vào bộ sưu tập mới được tạo trong danh sách mới được tạo yêu cầu gọi "Thêm". Vì "Add" là void-return, một cuộc gọi đến nó chỉ có thể xuất hiện trong một câu lệnh biểu thức. Tính năng này cho phép bạn tạo một bộ sưu tập mới (với "mới") và điền vào nó, hoặc điền vào một bộ sưu tập hiện có (không có "mới"), trong đó bộ sưu tập là thành viên của đối tượng mà bạn đang tạo là kết quả của LINQ truy vấn.

+0

Cảm ơn bạn đã trả lời rất trực tiếp cho câu hỏi của tôi. Như tôi cũng đã nhận xét về các câu trả lời khác, nó hợp lý trong hành vi của nó trong ngữ cảnh về những gì chúng ta muốn có thể làm, mặc dù nó không phải là rất hợp lý trong cú pháp của nó như một người mong đợi một phép toán '=' thực hiện một phép gán chính xác . Động lực LINQ của bạn góp phần vào điều này, và tôi có thể thấy lý do tại sao họ quyết định thiết kế nó theo cách này. – Mika

+0

Thêm nó vào tài liệu MSDN tại http://msdn.microsoft.com/en-us/library/bb384062.aspx sẽ là tốt đẹp. Nó chỉ hiển thị các biến thể khác của bộ tạo/bộ khởi tạo đối tượng. – CodesInChaos

+0

@Mika ý nghĩa của '=' trong mã của bạn khớp với '=' trên bộ khởi tạo bộ sưu tập bình thường. Điều bất thường về mã của bạn là nó không có 'Danh sách mới ' trong đó. – CodesInChaos

Các vấn đề liên quan