2010-10-04 74 views
13

Trong C#, nếu bạn có một struct như vậy:C#: Tại sao đột biến trên cấu trúc chỉ đọc không bị phá vỡ?

struct Counter 
{ 
    private int _count; 

    public int Value 
    { 
     get { return _count; } 
    } 

    public int Increment() 
    { 
     return ++_count; 
    } 
} 

Và bạn có một chương trình như vậy:

static readonly Counter counter = new Counter(); 

static void Main() 
{ 
    // print the new value from the increment function 
    Console.WriteLine(counter.Increment()); 
    // print off the value stored in the item 
    Console.WriteLine(counter.Value); 
} 

Đầu ra của chương trình sẽ là:

1 
0 

Điều này có vẻ hoàn toàn sai. Tôi có thể mong đợi đầu ra là hai giây (vì đó là nếu Counterclass hoặc nếu struct Counter : ICountercounterICounter) hoặc là lỗi biên dịch. Tôi nhận ra rằng việc phát hiện điều này tại thời điểm biên dịch là một vấn đề khá khó khăn, nhưng hành vi này dường như vi phạm logic.

Có lý do nào cho hành vi này vượt quá khó khăn khi triển khai không?

+10

Xem [Làn sóng chỉ đọc đột biến "của Eric Lippert] (http://blogs.msdn.com/b/ericlippert/archive/2008/05/14/mutating-readonly-structs.aspx) –

+0

* Tại sao * bạn mong đợi hai giây? Bạn nói rằng bạn muốn nó được chỉ đọc, vậy tại sao bạn muốn nó thay đổi? –

Trả lời

8

structs là các loại giá trị và do đó có loại sematics có giá trị. Điều này có nghĩa là mỗi khi bạn truy cập vào cấu trúc, về cơ bản bạn làm việc với một bản sao của giá trị của struct.

Trong mẫu của bạn, bạn không thay đổi bản gốc struct mà chỉ là bản sao tạm thời của nó.

Xem ở đây để giải thích thêm:

Why are mutable structs evil

+4

Nhưng nếu bạn lấy đi 'readonly', thì kết quả mong đợi của hai giây sẽ xảy ra. Vì vậy, nó rõ ràng không làm một bản sao trong trường hợp đó. Điều này có nghĩa là tất cả các hàm được gọi trên các cấu trúc chỉ đọc tạo ra các bản sao của cấu trúc (vì C# không có khái niệm về một hàm biến đổi)? –

+4

Có, đó là trường hợp theo mục 7.5.4 của đặc tả ngôn ngữ C#. Xem bài viết của Eric Lippert về chủ đề này để biết thêm chi tiết. –

+1

Một điểm yếu thiết kế không may trong .net là không có phương tiện mà theo đó các phương thức struct có thể cho biết liệu chúng có thay đổi 'this' hay không. Vì nó sẽ gây phiền nhiễu nếu người ta không thể sử dụng bất kỳ phương thức hay thuộc tính nào trên cấu trúc chỉ đọc, và nó cũng sẽ gây phiền nhiễu nếu 'readonly' modifiers trên structs không được vinh danh, C# và vb.net thay vì" thỏa hiệp ", bởi có các lời gọi phương thức trên các cấu trúc chỉ đọc tạo một bản sao của cấu trúc trước khi gọi phương thức. Lưu ý rằng nếu một trường phơi bày các trường struct trực tiếp, trình biên dịch sẽ có thể phân biệt các truy cập đọc từ các ghi, và ... – supercat

3

Trong .net, một phương pháp dụ struct là ngữ nghĩa tương đương với một phương pháp cấu trúc tĩnh với một một ref tham số phụ của struct kiểu. Như vậy, với tuyên bố:

struct Blah { 
    public int value; 
    public void Add(int Amount) { value += Amount; } 
    public static void Add(ref Blah it; int Amount; it.value += Amount;} 
} 

Phương pháp này gọi là:

someBlah.Add(5); 
Blah.Add(ref someBlah, 5); 

ngữ nghĩa tương đương, ngoại trừ một sự khác biệt: các cuộc gọi sau này sẽ chỉ được phép nếu someBlah là một vị trí lưu trữ có thể thay đổi (biến, lĩnh vực, vv) và không nếu nó là một vị trí lưu trữ chỉ đọc, hoặc một giá trị tạm thời (kết quả của việc đọc một tài sản, vv).

Điều này đối diện với các nhà thiết kế ngôn ngữ .net: không cho phép sử dụng bất kỳ chức năng thành viên nào trên cấu trúc chỉ đọc sẽ gây khó chịu, nhưng họ không muốn cho phép các hàm thành viên ghi vào các biến chỉ đọc. Họ quyết định "punt", và làm cho nó để gọi một thể hiện phương thức trên một cấu trúc chỉ đọc sẽ tạo một bản sao của cấu trúc, gọi hàm trên đó, và sau đó loại bỏ nó. Điều này có tác dụng làm chậm các cuộc gọi đến các phương thức ví dụ không viết cấu trúc bên dưới và làm cho nó cố gắng sử dụng phương thức cập nhật cấu trúc cơ bản trên cấu trúc chỉ đọc sẽ mang lại ngữ nghĩa bị hỏng khác nhau từ sẽ đạt được nếu nó được truyền trực tiếp. Lưu ý rằng thời gian thêm được thực hiện bởi bản sao sẽ hầu như không bao giờ mang lại ngữ nghĩa chính xác trong các trường hợp không đúng nếu không có bản sao.

Một trong những mối quan tâm chính của tôi trong .net là vẫn còn (ít nhất là 4.0 và có lẽ 4.5) vẫn không có thuộc tính qua đó hàm thành viên cấu trúc có thể cho biết liệu nó có sửa đổi this hay không.Con người đường sắt về cách cấu trúc nên được bất biến, thay vì cung cấp các công cụ để cho phép cấu trúc để cung cấp một cách an toàn phương pháp đột biến. Điều này, mặc dù thực tế rằng cái gọi là cấu trúc "không thay đổi" là một lời nói dối. Tất cả các loại giá trị không tầm thường trong các vị trí lưu trữ có thể thay đổi đều có thể thay đổi được vì tất cả các loại giá trị được đóng hộp. Tạo cấu trúc "không thay đổi" có thể buộc phải viết lại toàn bộ cấu trúc khi chỉ muốn thay đổi một trường, nhưng từ struct1 = struct2 biến đổi struct1 bằng cách sao chép tất cả các trường công khai và riêng tư từ struct2 và không có định nghĩa kiểu cho cấu trúc có thể làm để ngăn chặn điều đó (ngoại trừ không có bất kỳ trường nào) nó không làm gì để ngăn chặn đột biến đột biến của các thành viên struct. Hơn nữa, bởi vì các vấn đề luồng, cấu trúc rất hạn chế trong khả năng của họ để thực thi bất kỳ loại mối quan hệ bất biến giữa các lĩnh vực của họ. IMHO, nói chung sẽ tốt hơn cho một cấu trúc để cho phép truy cập trường tùy ý, làm rõ rằng bất kỳ mã nào nhận được cấu trúc phải kiểm tra xem các trường của nó có đáp ứng tất cả các điều kiện được yêu cầu không, hơn là cố gắng ngăn chặn sự hình thành các cấu trúc không đáp ứng điều kiện.

+0

* '" struct1 = struct2' biến đổi struct1 bằng cách sao chép "* - đó là sự thật, nhưng có bất kỳ vấn đề thực tế với điều đó (ngoại trừ việc nó thường không nguyên tử)? Cả hai 'struct1' và' struct2' là các biến hoặc các trường được điền đầy dữ liệu thực tế, vì vậy sao chép là những gì được mong đợi trong trường hợp này vì đây là cách cấu trúc phân công hoạt động trong C và C++ theo mặc định. Trừ khi 'struct1' là một trường' readonly', trong trường hợp đó nó sẽ thất bại tại thời gian biên dịch. – Groo

+0

Dù sao, +1 cho thông tin hữu ích, nhưng tôi sẽ nói một chút về phần mà bạn nói rằng * gọi phương thức thể hiện trên cấu trúc chỉ đọc sẽ tạo bản sao của cấu trúc *, vì nó là * truy cập * một 'chỉ đọc 'struct field tạo một bản sao của cấu trúc đó. Nếu bạn đọc giá trị vào biến cục bộ trước, bạn có thể thay đổi nó (miễn là các trường bên trong của nó có thể thay đổi) và .NET sẽ hoạt động trực tiếp trên cấu trúc chồng ("chi tiết triển khai", tôi biết). Khi một trường không phải là 'readonly', thì các phương thức có thể hoạt động trực tiếp trên trường mà không cần sao chép nó. – Groo

+0

@Groo: Không có nhiều trường hợp góc quan trọng khi 'struct1 = struct2' thay đổi * existing * struct1 bằng cách ghi đè tất cả các trường của nó, nhưng cách duy nhất có thể hiểu chính xác những trường hợp góc đó là hiểu thực sự xảy ra. Đối với điểm thứ hai của bạn, tôi không hoàn toàn rõ ràng những gì bạn đang đề xuất; cụm từ "cấu trúc chỉ đọc" đề cập đến cả hai biến có một vòng loại 'chỉ đọc' và cũng là các vị trí lưu trữ tạm thời, chẳng hạn như giá trị trả về hàm hoặc thuộc tính; Tôi có cần lưu ý rằng các giá trị trả về của các thuộc tính đọc/ghi thậm chí là chỉ đọc không? – supercat

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