2012-04-10 38 views
5

Tự động tôi chọn giữa cấu trúc và lớp không phải do vấn đề bộ nhớ nhưng do ngữ nghĩa của loại. Một số loại giá trị của tôi có dung lượng bộ nhớ khá lớn, đôi khi quá lớn để sao chép dữ liệu này mọi lúc. Vì vậy, tôi tự hỏi nếu nó là một ý tưởng tốt để vượt qua bất biến giá trị đối tượng luôn luôn bằng cách tham khảo? Vì các đối tượng không thay đổi nên chúng không thể sửa đổi bằng các phương thức chấp nhận chúng bằng tham chiếu. Có các vấn đề khác khi đi qua tham chiếu không?Truyền các loại giá trị bất biến theo tham chiếu theo mặc định

+4

có thể bạn cần phải suy nghĩ lại lý do của mình để quyết định giữa các cấu trúc và lớp học. –

Trả lời

14

Một số kiểu giá trị của tôi có bộ nhớ khá lớn

Điều đó cho thấy họ không nên loại giá trị, từ một điểm thực hiện xem. Từ "Hướng dẫn thiết kế cho phát triển Class Libraries", phần "Choosing Between Classes And Structures":

Đừng định nghĩa một cấu trúc trừ loại có tất cả các đặc điểm sau:

  • Nó logic đại diện cho một giá trị duy nhất, tương tự như nguyên thủy các loại (số nguyên, gấp đôi, vv).
  • Đô thị này có kích thước mẫu nhỏ hơn 16 byte.
  • Điều này là không thay đổi.
  • Nó sẽ không phải được đóng hộp thường xuyên.

Có vẻ như bạn cần phải tạo ra bất biến tham khảo loại để thay thế. Trong nhiều cách họ kết thúc "cảm giác" như đối tượng giá trị anyway (nghĩ dây) nhưng bạn sẽ không cần phải lo lắng về hiệu quả của việc truyền chúng xung quanh.

"Bất Biến" với nhiều loại giá trị là một khái niệm hơi chất lỏng - và chắc chắn nó không có nghĩa là sử dụng ref là an toàn:

// int is immutable, right? 
int x = 5; 
Foo(ref x); 
Console.WriteLine(x); // Eek, prints 6... 
... 
void Foo(ref int y) 
{ 
    y = 6; 
} 

Chúng tôi không thay đổi một phần giá trị - chúng tôi đang thay thế toàn bộ giá trị của x bằng một giá trị hoàn toàn khác.

tính bất biến là hơi dễ dàng hơn để suy nghĩ về khi nói đến tham khảo các loại - mặc dù thậm chí sau đó bạn có thể có một đối tượng mà của riêng mình sẽ không thay đổi, nhưng có thể tham khảo các đối tượng có thể thay đổi ...

+0

"Nó có kích thước cá thể nhỏ hơn 16 byte". Bạn có thực sự viết một 'lớp Vector3d', bởi vì nó có 24 byte? – hansmaad

+2

@hansmaad Nó chỉ là một hướng dẫn. Mỗi trường hợp không thể được bao phủ bởi nó, nhưng họ là những điều/quy tắc để xem xét khi đưa ra quyết định. – vcsjones

+3

@hansmaad: Nếu loại có thể được * truyền theo giá trị * rất nhiều, thì * có *, bạn nên làm như vậy. Nếu kiểu giá trị không được truyền theo giá trị - nếu bạn chỉ tạo một giá trị và sử dụng nó một cách cục bộ mà không cần chuyển nó sang phương thức khác - hãy tiếp tục và làm cho nó lớn nếu bạn muốn. –

3

Vì vậy, tôi tự hỏi nếu bạn có nên vượt qua các đối tượng giá trị không thay đổi luôn bằng cách tham chiếu không? Vì các đối tượng không thay đổi nên chúng không thể sửa đổi bằng các phương thức chấp nhận chúng bằng tham chiếu. Có các vấn đề khác khi đi qua tham chiếu không?

Không rõ ý bạn là gì. Giả sử rằng bạn có nghĩa là chuyển nó như một tham số ref hoặc out, thì phương thức chỉ có thể gán một cá thể mới cho vị trí lưu trữ. Điều này sẽ sửa đổi những gì người gọi thấy vì vị trí lưu trữ trong callee là bí danh cho vị trí lưu trữ được người gọi truyền.

Nếu bạn đang xử lý các vấn đề về bộ nhớ vì sao chép các phiên bản struct xung quanh, bạn nên cân nhắc tạo một loại tham chiếu không thay đổi, giống như string.

11

câu trả lời Jon là tất nhiên đúng; Tôi sẽ thêm điều này vào nó: các loại giá trị là đã được chuyển qua tham chiếu khi bạn gọi một phương thức trên loại giá trị. Ví dụ:

struct S 
{ 
    int x; 
    public S(int x) { this.x = x; } 
    public void M() { Console.WriteLine(this.x); } 
} 

Phương pháp M() là một cách logic điều tương tự như:

public static void M(ref S _this) { Console.WriteLine(_this.x); } 

Bất cứ khi nào bạn gọi một phương pháp dụ về một cấu trúc, chúng tôi vượt qua một ref đến biến rằng là người nhận cuộc gọi.

Vậy điều gì xảy ra nếu người nhận không phải là biến? Sau đó, giá trị được sao chép vào biến tạm thời được sử dụng làm bộ thu. Và nếu giá trị lớn, đó có thể là một bản sao đắt tiền!

Loại giá trị được sao chép theo giá trị; đó là lý do tại sao chúng được gọi là các loại giá trị. Trừ khi bạn đang có kế hoạch là cực kỳ cẩn thận về việc tìm kiếm tất cả các bản sao đắt tiền có thể và loại bỏ chúng, tôi sẽ làm theo lời khuyên của hướng dẫn thiết kế khuôn khổ: giữ cấu trúc dưới 16 byte và chuyển chúng theo giá trị.

Tôi cũng nhấn mạnh rằng Jon nói đúng: chuyển một cấu trúc bằng ref có nghĩa là chuyển tham chiếu đến biến và các biến số có thể thay đổi. Đó là lý do tại sao chúng được gọi là "biến". Không có "const ref" trong C# theo cách có trong C++; ngay cả khi chính loại giá trị đó dường như là "không thay đổi" không có nghĩa là biến số giữ nó là không thay đổi. Bạn có thể thấy một ví dụ cực đoan về điều đó trong ví dụ mang tính giáo dục nhưng khó hiểu này:

struct S 
{ 
    readonly int x; 
    public S(int x) { this.x = x; } 
    public void M(ref S s) 
    { 
     Console.WriteLine(this.x); 
     s = new S(this.x + 1); 
     Console.WriteLine(this.x); 
    } 
} 

M có thể viết ra hai số khác nhau không? Bạn sẽ ngây thơ nghĩ rằng cấu trúc là bất biến, và do đó x không thể thay đổi. Nhưng cả hai snàybiến, và biến có thể thay đổi:

S q = new S(1); 
q.M(ref q); 

Đó in 1, 2 vì thiss đều tham chiếu đến q, và không có gì là dừng q từ việc thay đổi ; không phải là chỉ đọc.

Tóm lại: nếu tôi có rất nhiều dữ liệu mà tôi muốn đi qua và đảm bảo chắc chắn rằng nó không thay đổi, tôi sẽ sử dụng một lớp, không phải là cấu trúc.Chỉ sử dụng một cấu trúc trong kịch bản đó nếu bạn có một vấn đề hiệu suất được chứng minh thực sự được giải quyết bằng cách làm cho nó là cấu trúc, hãy nhớ rằng các cấu trúc lớn có khả năng rất tốn kém để sao chép.

+0

Cái gọi là cấu trúc bất biến có thể được hiển thị để có thể thay đổi ngay cả khi không có gì trong mã của chúng hợp tác trong đột biến như vậy. Ví dụ, mặc dù vị trí lưu trữ của 'KeyValuePair ' có thể được viết nguyên tử (vì phải mất chính xác 32 bit), có một chuỗi 'ToString()' trên một vị trí lưu trữ trong khi một luồng khác cập nhật giá trị có thể gây ra 'ToString 'để trả về' Khóa.ToString() 'cũ được nối với' Giá trị.ToString() 'mới. – supercat

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