2012-07-10 68 views
5

Tôi đã tìm ra một thứ khá khó hiểu trong C# gần đây. Trong cơ sở mã của chúng tôi, chúng tôi có một lớp học TreeNode. Khi thay đổi một số mã, tôi thấy rằng không thể gán biến cho thuộc tính Nodes. Khi kiểm tra kỹ hơn, nó trở nên rõ ràng rằng tài sản là chỉ đọc và hành vi này được mong đợi.Thiết lập thuộc tính chỉ đọc với loại vô danh

Điều kỳ lạ là cơ sở mã của chúng tôi đã có từ trước đến nay luôn dựa vào việc gán một số loại ẩn danh cho thuộc tính Nodes và được biên soạn và hoạt động hoàn hảo.

Để tóm tắt: tại sao bài tập ở số AddSomeNodes hoạt động ngay từ đầu?

using System.Collections.Generic; 

namespace ReadOnlyProperty 
{ 
    public class TreeNode 
    { 
     private readonly IList<TreeNode> _nodes = new List<TreeNode>(); 

     public IList<TreeNode> Nodes 
     { 
      get { return _nodes; } 
     } 
    } 

    public class TreeBuilder 
    { 
     public IEnumerable<TreeNode> AddSomeNodes() 
     { 
      yield return new TreeNode 
      { 
       Nodes = { new TreeNode() } 
      }; 
     } 

     public IEnumerable<TreeNode> AddSomeOtherNodes() 
     { 
      var someNodes = new List<TreeNode>(); 

      yield return new TreeNode 
      { 
       Nodes = someNodes 
      }; 
     } 
    } 
} 

Trả lời

4

AddSomeNodes không tạo ra một thể hiện của List<TreeNode> vì cú pháp đó là một khởi tạo bộ sưu tập (do đó nó không được gán cho Nodes ý nghĩa nó không phá vỡ hợp đồng readonly), trình biên dịch thực sự dịch bộ khởi tạo bộ sưu tập thành các cuộc gọi đến .Add.

Cuộc gọi AddSomeOtherNodes thực sự cố gắng chỉ định lại giá trị, nhưng nó là readonly. Đây cũng là cú pháp khởi tạo đối tượng, được dịch thành các cuộc gọi thuộc tính đơn giản. Thuộc tính này không có setter, do đó cuộc gọi đó tạo ra một lỗi trình biên dịch. Cố gắng thêm một setter đặt giá trị chỉ đọc sẽ tạo ra một lỗi trình biên dịch khác vì nó được đánh dấu readonly.

From MSDN:

Bằng cách sử dụng một initializer bộ sưu tập bạn không cần phải xác định nhiều cuộc gọi đến các phương thức Add của lớp trong mã nguồn của bạn; trình biên dịch thêm các cuộc gọi.

Ngoài ra, chỉ cần làm rõ, có không loại vô danh chơi trong mã của bạn trong - đó là tất cả các cú pháp khởi tạo.


Không liên quan đến câu hỏi của bạn, nhưng trong cùng một khu vực.

Điều thú vị là Nodes = { new TreeNode() } cú pháp không làm việc với một thành viên địa phương, nó dường như chỉ làm việc khi nó được lồng vào bên trong một initializer đối tượng hoặc trong quá trình phân công đối tượng:

List<int> numbers = { 1, 2, 3, 4 }; // This isn't valid. 
List<int> numbers = new List<int> { 1, 2, 3, 4 }; // Valid. 

// This is valid, but will NullReferenceException on Numbers 
// if NumberContainer doesn't "new" the list internally. 
var container = new NumberContainer() 
{ 
    Numbers = { 1, 2, 3, 4 } 
}; 

Các tài liệu MSDN dường như không để làm rõ về điều này.

+0

@Downvoter Tại sao lại là downvote? Quan sát của tôi về tình hình OPs là chính xác theo tài liệu và thử nghiệm của riêng tôi. Chưa kể tất cả các câu trả lời khác. –

+0

Tôi vừa nhận được phiếu bầu xuống! – MBen

+0

Cảm ơn bạn đã cung cấp câu trả lời đầy đủ và ngắn gọn như vậy một cách nhanh chóng! Bạn hoàn toàn đúng rằng tôi đã nhầm lẫn khi gọi nó là một loại vô danh. Hành vi này có ý nghĩa khi xem xét nó là cú pháp khởi tạo bộ sưu tập, chứ không phải gán kiểu ẩn danh. Đôi khi rất khó để phân biệt các khái niệm này một cách tinh thần bởi vì chúng rất giống nhau. Cảm ơn! – Michiel

1

này không được gán cho nó là thêm một yếu tố để các ICollection

Nodes = {new TreeNode() }, that is why it works. 
2

Thuộc tính nút của bạn không được gán.

Sử dụng initializer bộ sưu tập đặc biệt:

CollectionProperty = { a, b, c }; 

được thay đổi thành:

CollectionProperty.Add(a); 
CollectionProperty.Add(b); 
CollectionProperty.Add(c); 
0

Trình biên dịch tương đương với những gì đã xảy ra là thế này:

public IEnumerable<TreeNode> AddSomeNodes() 
{ 
    TreeNode node = new TreeNode(); 
    node.Nodes.Add(new TreeNode()); 

    yield return node; 
} 

Sự khác biệt quan trọng ở đây là họ đã sử dụng cú pháp trình khởi tạo bộ sưu tập để gán giá trị.

1

tôi biên soạn mã của bạn (sau khi loại bỏ các phương pháp AddSomeOtherNodes) và mở nó trong Reflector và đây là kết quả:

public IEnumerable<TreeNode> AddSomeNodes() 
{ 
    TreeNode iteratorVariable0 = new TreeNode(); 
    iteratorVariable0.Nodes.Add(new TreeNode()); 
    yield return iteratorVariable0; 
} 

Như bạn có thể thấy, với điều này cú pháp Add phương pháp được gọi là trên biến Nodes .

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