Câu hỏi Khi sử dụng struct?


Khi nào bạn nên sử dụng struct và không phải lớp trong C #? Mô hình khái niệm của tôi là cấu trúc được sử dụng trong thời gian khi mục đó chỉ đơn thuần là một tập hợp các loại giá trị. Một cách hợp lý giữ tất cả chúng lại với nhau thành một tổng thể gắn kết.

Tôi đã xem qua các quy tắc này đây:

  • Một cấu trúc nên đại diện cho một giá trị.
  • Cấu trúc nên có bộ nhớ dấu chân dưới 16 byte.
  • Cấu trúc không được thay đổi sau sự sáng tạo.

Các quy tắc này có hoạt động không? Cấu trúc có ý nghĩa gì?


1201
2018-02-06 17:37


gốc


System.Drawing.Rectangle vi phạm cả ba quy tắc này. - ChrisW
Có, cũng phần nào trong số họ. Tôi biết nó được sử dụng cho các phần của trò chơi, như NWN2 World Creator. C vẫn thường ở lõi (động cơ). XNA Game Studio, Google nó :) - Refracted Paladin
có khá nhiều trò chơi thương mại được viết bằng C #, vấn đề là chúng được sử dụng cho mã được tối ưu hóa - BlackTigerX
Các cấu trúc cung cấp hiệu suất tốt hơn khi bạn có các bộ sưu tập nhỏ các kiểu giá trị mà bạn muốn nhóm lại với nhau. Điều này xảy ra tất cả các thời gian trong lập trình game, ví dụ, một đỉnh trong một mô hình 3D sẽ có một vị trí, kết cấu phối hợp và bình thường, nó cũng thường sẽ không thay đổi. Một mô hình duy nhất có thể có một vài nghìn đỉnh, hoặc nó có thể có một tá, nhưng các cấu trúc cung cấp ít chi phí tổng thể hơn trong kịch bản sử dụng này. Tôi đã xác minh điều này thông qua thiết kế động cơ của riêng tôi. - Chris D.
@ErikForbes: Tôi nghĩ điều này thường được tổ chức dưới dạng BCL lớn nhất "oops" - Will


Các câu trả lời:


Các nguồn tham chiếu của OP có một số tín nhiệm ... nhưng những gì về Microsoft - lập trường về sử dụng struct là gì? Tôi đã tìm thêm một số học từ Microsoftvà đây là những gì tôi đã tìm thấy:

Xem xét xác định cấu trúc thay vì một lớp nếu các phiên bản của   loại nhỏ và thường ngắn ngủi hoặc thường được nhúng vào   các đối tượng khác.

Không xác định cấu trúc trừ khi loại có tất cả các đặc điểm sau: 

  1. Nó biểu diễn một cách hợp lý một giá trị duy nhất, tương tự như các kiểu nguyên thủy (số nguyên, gấp đôi, vv).
  2. Nó có kích thước cá thể nhỏ hơn 16 byte.
  3. Nó là bất biến.
  4. Nó sẽ không phải được đóng hộp thường xuyên.

Microsoft luôn vi phạm những quy tắc đó

Được rồi, # 2 và # 3. Từ điển yêu quý của chúng tôi có 2 cấu trúc bên trong:

[StructLayout(LayoutKind.Sequential)]  // default for structs
private struct Entry  //<Tkey, TValue>
{
    //  View code at *Reference Source
}

[Serializable, StructLayout(LayoutKind.Sequential)]
public struct Enumerator : 
    IEnumerator<KeyValuePair<TKey, TValue>>, IDisposable, 
    IDictionaryEnumerator, IEnumerator
{
    //  View code at *Reference Source
}

*Nguồn tham khảo

Nguồn 'JonnyCantCode.com' có 3 trong số 4 - khá đáng tha thứ vì # 4 có thể sẽ không là vấn đề. Nếu bạn thấy mình đấm bốc một cấu trúc, hãy suy nghĩ lại kiến ​​trúc của bạn.

Hãy xem tại sao Microsoft sẽ sử dụng các cấu trúc sau:

  1. Mỗi cấu trúc, Entry và Enumerator, đại diện cho các giá trị đơn.
  2. Tốc độ
  3. Entry không bao giờ được chuyển thành tham số bên ngoài lớp Từ điển. Điều tra thêm cho thấy rằng để đáp ứng việc thực hiện IEnumerable, từ điển sử dụng Enumerator struct mà nó sao chép mỗi khi một điều tra viên được yêu cầu ... có ý nghĩa.
  4. Nội bộ cho lớp Từ điển. Enumerator là công khai vì Từ điển được liệt kê và phải có khả năng tiếp cận tương đương với việc triển khai giao diện IEnumerator - ví dụ: IEnumerator getter.

Cập nhật - Ngoài ra, nhận ra rằng khi một cấu trúc thực hiện một giao diện - như Enumerator hiện - và được đúc vào loại được triển khai đó, cấu trúc trở thành một kiểu tham chiếu và được di chuyển đến heap. Nội bộ cho lớp Từ điển, Điều tra viên  vẫn là một loại giá trị. Tuy nhiên, ngay khi có cuộc gọi phương thức GetEnumerator(), kiểu tham chiếu IEnumerator Được trả lại.

Những gì chúng ta không thấy ở đây là bất kỳ nỗ lực hoặc bằng chứng về yêu cầu nào để giữ cấu trúc không thay đổi hoặc duy trì kích thước cá thể chỉ có 16 byte hoặc ít hơn:

  1. Không có gì trong các cấu trúc trên được khai báo readonly - - không phải không thay đổi
  2. Kích thước của các cấu trúc này có thể vượt quá 16 byte
  3. Entry có một cuộc đời chưa được xác định (từ Add(), đến Remove(), Clear(), hoặc thu gom rác thải);

Và ...  4. Cả hai cấu trúc lưu trữ TKey và TValue, mà tất cả chúng ta đều biết là khá có khả năng là các kiểu tham chiếu (thêm thông tin tiền thưởng)

Các phím đã được khắc phục, mặc dù các từ điển nhanh chóng một phần vì việc căn chỉnh cấu trúc nhanh hơn loại tham chiếu. Ở đây, tôi có một Dictionary<int, int> lưu trữ 300.000 số nguyên ngẫu nhiên với các phím tăng dần theo tuần tự.

Công suất: 312874
  MemSize: 2660827 byte
  Đã hoàn thành thay đổi kích thước: 5 mili giây
  Tổng thời gian để điền: 889ms

Sức chứa: số lượng phần tử có sẵn trước khi mảng nội bộ phải được thay đổi kích thước.

MemSize: được xác định bằng cách tuần tự hóa từ điển thành MemoryStream và nhận được độ dài byte (đủ chính xác cho mục đích của chúng tôi).

Đã đổi kích thước hoàn tất: thời gian cần để thay đổi kích thước mảng nội bộ từ 150862 thành phần tử thành 312874. Khi bạn thấy rằng mỗi phần tử được sao chép tuần tự qua Array.CopyTo(), đó không phải là quá tồi tàn.

Tổng thời gian để điền: thừa nhận sai lệch do đăng nhập và OnResize sự kiện tôi đã thêm vào nguồn; Tuy nhiên, vẫn còn ấn tượng để lấp đầy 300k số nguyên trong khi thay đổi kích thước 15 lần trong quá trình hoạt động. Chỉ vì tò mò, tổng thời gian để lấp đầy là gì nếu tôi đã biết khả năng? 13 mili giây 

Vì vậy, bây giờ, nếu Entry là một lớp học? Những thời gian hoặc số liệu này có thực sự khác nhau nhiều không?

Công suất: 312874
  MemSize: 2660827 byte
  Đã thay đổi kích thước: 26 mili giây
  Tổng thời gian để điền: 964ms

Rõ ràng, sự khác biệt lớn là thay đổi kích thước. Bất kỳ sự khác biệt nào nếu từ điển được khởi tạo với dung lượng? Không đủ quan tâm ... 12 mili giây.

Điều gì xảy ra là, bởi vì Entry là một cấu trúc, nó không yêu cầu khởi tạo như một kiểu tham chiếu. Đây là cả vẻ đẹp và bản chất của loại giá trị. Để sử dụng Entry làm kiểu tham chiếu, tôi phải chèn mã sau:

/*
 *  Added to satisfy initialization of entry elements --
 *  this is where the extra time is spent resizing the Entry array
 * **/
for (int i = 0 ; i < prime ; i++)
{
    destinationArray[i] = new Entry( );
}
/*  *********************************************** */  

Lý do tôi phải khởi tạo từng phần tử mảng Entry như một loại tham chiếu có thể được tìm thấy tại MSDN: Thiết kế cấu trúc. Nói ngắn gọn:

Không cung cấp một hàm tạo mặc định cho một cấu trúc.

Nếu một cấu trúc định nghĩa một hàm tạo mặc định, khi các mảng của   cấu trúc được tạo ra, thời gian chạy ngôn ngữ phổ biến tự động   thực thi hàm tạo mặc định trên mỗi phần tử mảng.

Một số trình biên dịch, chẳng hạn như trình biên dịch C #, không cho phép các cấu trúc   có các hàm tạo mặc định.

Nó thực sự khá đơn giản và chúng tôi sẽ mượn Asimov's Ba luật của Robotics:

  1. Cấu trúc phải an toàn để sử dụng
  2. Cấu trúc phải thực hiện chức năng của nó một cách hiệu quả, trừ khi điều này vi phạm quy tắc # 1
  3. Cấu trúc phải duy trì nguyên vẹn trong quá trình sử dụng trừ khi sự phá hủy của nó được yêu cầu để đáp ứng quy tắc # 1

...những gì chúng ta lấy đi từ này: ngắn gọn, chịu trách nhiệm với việc sử dụng các loại giá trị. Chúng nhanh chóng và hiệu quả, nhưng có khả năng gây ra nhiều hành vi bất ngờ nếu không được duy trì đúng cách (tức là các bản sao không chủ ý).


541
2017-08-07 13:44



Đối với các quy tắc của Microsoft, quy tắc về bất biến dường như được thiết kế để ngăn cản việc sử dụng các loại giá trị theo cách hành vi của chúng sẽ khác với các loại tham chiếu, mặc dù thực tế là các ngữ nghĩa giá trị có thể thay đổi được có thể hữu ích. Nếu có một loại có thể thay đổi theo thứ tự sẽ làm cho nó dễ dàng hơn để làm việc với, và nếu các vị trí lưu trữ của loại nên được tách ra một cách hợp lý với nhau, loại đó phải là một cấu trúc "có thể thay đổi". - supercat
Ghi nhớ rằng chỉ đọc! = không thay đổi. - Justin Morgan
Thực tế là nhiều loại của Microsoft vi phạm các quy tắc này không đại diện cho một vấn đề với các loại đó, mà đúng hơn chỉ ra rằng các quy tắc không nên áp dụng cho tất cả các loại cấu trúc. Nếu một cấu trúc đại diện cho một thực thể duy nhất [như với Decimal hoặc là DateTime], sau đó nếu nó sẽ không tuân thủ ba quy tắc khác, nó phải được thay thế bởi một lớp học. Nếu cấu trúc chứa tập hợp biến cố định, mỗi biến có thể giữ bất kỳ giá trị nào hợp lệ cho loại của nó [ví dụ: Rectangle], sau đó nó phải tuân theo khác nhau các quy tắc, một số trong đó trái ngược với các quy tắc cho các cấu trúc "một giá trị". - supercat
@IAbstract: Một số người sẽ biện minh cho Dictionary loại mục nhập dựa trên cơ sở đó là loại nội bộ duy nhất, hiệu suất được coi là quan trọng hơn ngữ nghĩa hoặc một số lý do khác. Quan điểm của tôi là một kiểu như Rectangle nên có nội dung của nó được hiển thị dưới dạng các trường riêng lẻ có thể chỉnh sửa chứ không phải vì "các lợi ích hiệu suất vượt quá các khuyết điểm ngữ nghĩa kết quả, nhưng vì loại ngữ nghĩa đại diện cho một tập hợp các giá trị độc lập cố địnhvà do đó cấu trúc có thể biến đổi vừa hiệu quả hơn vừa ngữ nghĩa hơn cấp trên. - supercat
@supercat: Tôi đồng ý ... và toàn bộ câu trả lời của tôi là 'hướng dẫn' khá yếu và cấu trúc nên được sử dụng với kiến ​​thức và hiểu biết đầy đủ về hành vi. Xem câu trả lời của tôi về cấu trúc có thể thay đổi ở đây: stackoverflow.com/questions/8108920/… - IAbstract


Bất cứ khi nào bạn không cần đa hình, muốn ngữ nghĩa giá trị, và muốn tránh phân bổ đống và phí thu gom rác liên quan. Tuy nhiên, báo trước là các cấu trúc (tùy ý lớn) đắt hơn để vượt qua các tham chiếu lớp (thường là một từ máy), vì vậy các lớp học có thể nhanh hơn trong thực tế.


142
2018-02-06 17:40



Đó chỉ là một "báo trước". Cũng nên xem xét "nâng" các loại giá trị và các trường hợp như (Guid)null (có thể bỏ một null vào một kiểu tham chiếu), trong số những thứ khác.
đắt hơn trong C / C ++? trong C ++ cách được khuyến nghị là truyền đối tượng theo giá trị - Ion Todirel
@ IonTodirel Đó không phải là lý do an toàn cho bộ nhớ, chứ không phải là hiệu suất? Nó luôn luôn là một thương mại-off, nhưng đi qua 32 B bởi stack luôn luôn (TM) sẽ được chậm hơn so với đi qua một tài liệu tham khảo 4 B bằng cách đăng ký. Tuy nhiên, cũng lưu ý rằng việc sử dụng "giá trị / tham chiếu" hơi khác một chút trong C # và C ++ - khi bạn chuyển một tham chiếu đến một đối tượng, bạn vẫn truyền theo giá trị, mặc dù bạn đang chuyển một tham chiếu (bạn chuyển giá trị của tham chiếu, không phải là tham chiếu đến tham chiếu, về cơ bản). Nó không phải là giá trị ngữ nghĩa, nhưng về mặt kỹ thuật là "giá trị theo giá trị". - Luaan
@ Luaan Sao chép chỉ là một khía cạnh của chi phí. Các indirection thêm do con trỏ / tham khảo cũng chi phí cho mỗi truy cập. Trong một số trường hợp, cấu trúc thậm chí có thể được di chuyển và do đó thậm chí không cần phải được sao chép. - Onur
@Onur đó là thú vị. Làm thế nào để bạn "di chuyển" mà không cần sao chép? Tôi nghĩ rằng lệnh "mov" asm không thực sự "di chuyển". Nó sao chép. - Winger Sendon


Tôi không đồng ý với các quy tắc được đưa ra trong bài đăng gốc. Dưới đây là các quy tắc của tôi:

1) Bạn sử dụng cấu trúc cho hiệu suất khi được lưu trữ trong mảng. (Xem thêm Khi nào xây dựng câu trả lời?)

2) Bạn cần chúng trong mã truyền dữ liệu có cấu trúc đến / từ C / C ++

3) Không sử dụng cấu trúc trừ khi bạn cần chúng:

  • Chúng hoạt động khác với "đối tượng bình thường" (loại tham chiếu) theo phân công và khi chuyển qua làm đối số, điều này có thể dẫn đến hành vi không mong muốn; điều này đặc biệt nguy hiểm nếu người xem mã không biết họ đang đối phó với một cấu trúc.
  • Họ không thể được thừa hưởng.
  • Việc chuyển các cấu trúc như các đối số đắt hơn các lớp.

133
2018-02-28 16:33



+1 Có, tôi đồng ý hoàn toàn # 1 (đây là khổng lồ lợi thế khi giao dịch với những thứ như hình ảnh, v.v.) và chỉ ra rằng chúng khác nhau từ "đối tượng bình thường" và có biết cách biết điều này ngoại trừ kiến ​​thức hiện có hoặc tự kiểm tra loại. Ngoài ra, bạn không thể bỏ một giá trị null vào một kiểu struct :-) Đây thực sự là một trường hợp mà tôi hầu hết mong muốn có một số 'Hungary' cho các loại giá trị không phải lõi hoặc từ khóa 'struct' bắt buộc tại vị trí khai báo biến.
@ pst: Đúng là người ta phải biết điều gì đó là một struct để biết nó sẽ hoạt động như thế nào, nhưng nếu cái gì đó là struct với các lĩnh vực tiếp xúc, đó là tất cả những gì phải biết. Nếu một đối tượng phơi bày một thuộc tính của kiểu trường tiếp xúc, và nếu mã đọc cấu trúc đó đến biến và sửa đổi, người ta có thể dự đoán một cách an toàn rằng hành động đó sẽ không ảnh hưởng đến đối tượng có thuộc tính được đọc trừ khi hoặc cho đến khi cấu trúc được viết trở lại. Ngược lại, nếu thuộc tính là một loại lớp có thể thay đổi, đọc nó và sửa đổi nó có thể cập nhật đối tượng bên dưới như mong đợi, nhưng ... - supercat
... nó cũng có thể sẽ không thay đổi gì cả, hoặc nó có thể thay đổi hoặc làm hỏng các vật thể mà người ta không có ý định thay đổi. Có mã có ngữ nghĩa nói "thay đổi biến này tất cả các bạn thích; thay đổi sẽ không làm gì cả cho đến khi bạn lưu trữ chúng một cách rõ ràng" có vẻ rõ ràng hơn là mã có nội dung "Bạn đang tham chiếu đến một số đối tượng, có thể được chia sẻ với bất kỳ số nào các tài liệu tham khảo khác, hoặc có thể không được chia sẻ, bạn sẽ phải tìm ra những người khác có thể có tham chiếu đến đối tượng này để biết điều gì sẽ xảy ra nếu bạn thay đổi nó. " - supercat
Tiếp tục với # 1. Một danh sách đầy đủ các cấu trúc có thể siết chặt nhiều dữ liệu liên quan hơn vào cache L1 / L2 hơn một danh sách đầy đủ các tham chiếu đối tượng, (cho đúng cấu trúc kích thước). - Matt Stephenson
Thừa kế hiếm khi là công cụ thích hợp cho công việc và lý do quá nhiều về hiệu năng mà không cần lược tả là một ý tưởng tồi. Thứ nhất, các cấu trúc có thể được truyền qua tham chiếu. Thứ hai, đi qua tham chiếu hoặc theo giá trị hiếm khi là một vấn đề hiệu suất đáng kể. Cuối cùng, bạn không phải kế toán phân bổ đống bổ sung và thu gom rác thải cần phải diễn ra cho một lớp học. Cá nhân, tôi thích nghĩ về cấu trúc như dữ liệu cũ và các lớp học như những thứ làm mọi thứ (đối tượng) mặc dù bạn cũng có thể định nghĩa các phương thức trên các cấu trúc. - weberc2


Sử dụng cấu trúc khi bạn muốn ngữ nghĩa giá trị trái ngược với ngữ nghĩa tham chiếu.

Chỉnh sửa

Bạn không chắc chắn tại sao folks downvoting này nhưng đây là một điểm hợp lệ, và đã được thực hiện trước khi op làm rõ câu hỏi của mình, và nó là lý do cơ bản nhất cho cấu trúc.

Nếu bạn cần ngữ nghĩa tham chiếu, bạn cần một lớp không phải là cấu trúc.


82
2018-02-06 17:40



Tất cả mọi người biết rằng. Có vẻ như anh ấy đang tìm kiếm nhiều hơn một câu trả lời "struct is a value type". - TheSmurf
Không phải ai cũng biết điều đó. Đó rõ ràng là một trường hợp. - BobbyShaftoe
Đó là trường hợp cơ bản nhất và nên được tuyên bố cho bất cứ ai đọc bài đăng này và không biết điều đó. - JoshBerke
Không phải câu trả lời này không đúng; nó rõ ràng là. Đó không phải là vấn đề. - TheSmurf
@Josh: Đối với những ai chưa biết, chỉ cần nói rằng đó là một câu trả lời không đủ, vì rất có thể họ cũng không biết nó có nghĩa là gì. - TheSmurf


Ngoài câu trả lời "nó là một giá trị", một kịch bản cụ thể cho việc sử dụng cấu trúc là khi bạn biết rằng bạn có một tập hợp dữ liệu đang gây ra vấn đề thu gom rác và bạn có rất nhiều đối tượng. Ví dụ, một danh sách lớn / mảng các cá thể Person. Ẩn dụ tự nhiên ở đây là một lớp, nhưng nếu bạn có số lượng lớn cá thể người tồn tại lâu dài, chúng có thể làm tắc nghẽn GEN-2 và gây ra các gian hàng GC. Nếu kịch bản đảm bảo nó, một cách tiếp cận tiềm năng ở đây là sử dụng một mảng (không phải danh sách) của Person cấu trúc, I E. Person[]. Bây giờ, thay vì có hàng triệu đối tượng trong GEN-2, bạn có một đoạn duy nhất trên LOH (tôi giả sử không có chuỗi nào ở đây - nghĩa là một giá trị thuần túy không có bất kỳ tham chiếu nào). Điều này có rất ít tác động GC.

Làm việc với dữ liệu này là khó xử, vì dữ liệu có thể là quá kích thước cho một cấu trúc, và bạn không muốn sao chép các giá trị chất béo tất cả các thời gian. Tuy nhiên, việc truy cập nó trực tiếp trong một mảng không sao chép cấu trúc - nó được đặt tại chỗ (tương phản với một bộ chỉ mục danh sách, bản sao đó). Điều này có nghĩa là rất nhiều công việc với các chỉ mục:

int index = ...
int id = peopleArray[index].Id;

Lưu ý rằng việc giữ các giá trị không thay đổi sẽ giúp ích ở đây. Đối với logic phức tạp hơn, hãy sử dụng một phương thức có tham số by-ref:

void Foo(ref Person person) {...}
...
Foo(ref peopleArray[index]);

Một lần nữa, đây là tại chỗ - chúng tôi đã không sao chép giá trị.

Trong những tình huống rất cụ thể, chiến thuật này có thể rất thành công; Tuy nhiên, nó là một scernario khá tiên tiến mà nên được cố gắng chỉ khi bạn biết những gì bạn đang làm và tại sao. Mặc định ở đây sẽ là một lớp.


54
2017-10-22 12:14



+1 Câu trả lời thú vị. Bạn có sẵn lòng chia sẻ bất kỳ giai thoại thế giới thực nào về cách tiếp cận này đang được sử dụng không? - Jordão
@Jordao ở trên thiết bị di động, nhưng tìm kiếm google cho: + gravell + "assault by GC" - Marc Gravell♦
Cảm ơn rất nhiều. Tôi tìm thấy nó đây. - Jordão
@MarcGravell Tại sao bạn đề cập đến: sử dụng một mảng (không phải danh sách) ? List Tôi tin rằng, sử dụng một Array hậu trường. không? - Royi Namir
@RoyiNamir Tôi đã tò mò về điều này là tốt, nhưng tôi tin rằng câu trả lời nằm trong đoạn thứ hai của câu trả lời của Marc. "Tuy nhiên, việc truy cập nó trực tiếp trong một mảng không sao chép cấu trúc - nó được đặt tại chỗ (tương phản với một bộ chỉ mục danh sách, mà sao chép)." - user1323245


Từ C # Đặc tả ngôn ngữ:

1.7 Cấu trúc 

Giống như các lớp, cấu trúc là các cấu trúc dữ liệu có thể chứa các thành viên dữ liệu và các thành viên hàm, nhưng không giống như các lớp, các cấu trúc   các loại giá trị và không yêu cầu phân bổ đống. Một biến của một struct   loại trực tiếp lưu trữ dữ liệu của cấu trúc, trong khi một biến của   loại lớp lưu trữ một tham chiếu đến một đối tượng được phân bổ động.   Các loại cấu trúc không hỗ trợ thừa kế do người dùng chỉ định và tất cả các cấu trúc   các loại thừa kế hoàn toàn từ đối tượng kiểu.

Các cấu trúc đặc biệt hữu ích cho các cấu trúc dữ liệu nhỏ có   giá trị ngữ nghĩa. Số phức, điểm trong hệ tọa độ hoặc   cặp khóa-giá trị trong từ điển là tất cả các ví dụ hay về cấu trúc. Các   sử dụng các cấu trúc hơn là các lớp cho các cấu trúc dữ liệu nhỏ có thể làm cho   một sự khác biệt lớn về số lượng cấp phát bộ nhớ một ứng dụng   thực hiện. Ví dụ, chương trình sau tạo và khởi tạo   một mảng 100 điểm. Với điểm được thực hiện như một lớp, 101   các đối tượng riêng biệt được khởi tạo — một đối tượng cho mảng và mỗi đối tượng cho   100 yếu tố.

class Point
{
   public int x, y;

   public Point(int x, int y) {
      this.x = x;
      this.y = y;
   }
}

class Test
{
   static void Main() {
      Point[] points = new Point[100];
      for (int i = 0; i < 100; i++) points[i] = new Point(i, i);
   }
}

Một cách khác là làm cho Point a struct.

struct Point
{
   public int x, y;

   public Point(int x, int y) {
      this.x = x;
      this.y = y;
   }
}

Bây giờ, chỉ một đối tượng được khởi tạo — một đối tượng cho mảng — và các cá thể Point được lưu trữ trong dòng trong mảng.

Các hàm tạo cấu trúc được gọi với toán tử mới, nhưng điều đó không hàm ý rằng bộ nhớ đang được cấp phát. Thay vì phân bổ động một đối tượng và trả về một tham chiếu đến nó, một hàm tạo struct chỉ đơn giản trả về giá trị struct (thường ở vị trí tạm thời trên ngăn xếp), và giá trị này sau đó được sao chép khi cần thiết.

Với các lớp, có thể cho hai biến tham chiếu cùng một đối tượng và do đó có thể cho các phép toán trên một biến để ảnh hưởng đến đối tượng được tham chiếu bởi biến khác. Với các cấu trúc, mỗi biến có bản sao dữ liệu riêng của chúng và không thể thực hiện các thao tác trên một để ảnh hưởng đến dữ liệu kia. Ví dụ, đầu ra được tạo ra bởi đoạn mã sau đây phụ thuộc vào việc Point là một lớp hay một cấu trúc.

Point a = new Point(10, 10);
Point b = a;
a.x = 20;
Console.WriteLine(b.x);

Nếu Point là một lớp, đầu ra là 20 vì a và b tham chiếu cùng một đối tượng. Nếu Point là một cấu trúc, đầu ra là 10 vì gán a đến b tạo một bản sao của giá trị và bản sao này không bị ảnh hưởng bởi việc gán sau đó cho a.x.

Ví dụ trước nêu bật hai trong số các giới hạn của cấu trúc. Đầu tiên, việc sao chép toàn bộ cấu trúc thường kém hiệu quả hơn việc sao chép một tham chiếu đối tượng, do đó việc truyền tham số và giá trị có thể đắt hơn với cấu trúc hơn so với các kiểu tham chiếu. Thứ hai, ngoại trừ các tham số ref và out, không thể tạo tham chiếu đến các cấu trúc, quy tắc sử dụng chúng trong một số tình huống.


36
2017-09-17 15:42



Trong khi thực tế là tham chiếu đến cấu trúc không thể được tiếp tục tồn tại đôi khi là một hạn chế, nó cũng là một đặc tính rất hữu ích. Một trong những điểm yếu chính của .net là không có cách nào để vượt qua mã bên ngoài một tham chiếu đến một đối tượng có thể thay đổi mà không mất quyền kiểm soát đối tượng đó. Ngược lại, người ta có thể đưa ra một phương pháp bên ngoài một cách an toàn ref với một cấu trúc có thể thay đổi và biết rằng bất kỳ đột biến nào mà phương thức bên ngoài sẽ thực hiện trên nó sẽ được thực hiện trước khi nó trả về. Nó quá tệ .net không có bất kỳ khái niệm nào về các thông số tạm thời và các giá trị trả về hàm, vì ... - supercat
... điều đó sẽ cho phép các ngữ nghĩa thuận lợi của các cấu trúc truyền qua ref để đạt được với các đối tượng lớp. Về cơ bản, các biến địa phương, các tham số và các giá trị trả về hàm có thể tồn tại (mặc định), có thể trả lại hoặc không lâu. Mã sẽ bị cấm sao chép những thứ vô thời hạn vào bất cứ thứ gì có thể sống lâu hơn phạm vi hiện tại. Những thứ có thể trả lại sẽ giống như những thứ không lâu, ngoại trừ việc chúng có thể được trả về từ một hàm. Giá trị trả về của một hàm sẽ bị ràng buộc bởi các hạn chế chặt chẽ nhất có thể áp dụng cho bất kỳ tham số "trả về" nào của nó. - supercat


Structs là tốt cho đại diện nguyên tử của dữ liệu, nơi mà các dữ liệu cho biết có thể được sao chép nhiều lần bởi mã. Nhân bản một đối tượng nói chung là đắt hơn so với việc sao chép một cấu trúc, vì nó liên quan đến việc cấp phát bộ nhớ, chạy hàm khởi tạo và deallocating / garbage collection khi thực hiện với nó.


31
2018-02-06 17:58



Có, nhưng các cấu trúc lớn có thể tốn kém hơn so với các tham chiếu lớp (khi đi qua các phương thức). - Alex


Đây là một quy tắc cơ bản.

  • Nếu tất cả các trường thành viên là các kiểu giá trị, hãy tạo một cấu trúc.

  • Nếu bất kỳ trường thành viên nào là loại tham chiếu, hãy tạo lớp học. Điều này là do trường kiểu tham chiếu sẽ cần phân bổ heap.

Exmaples

public struct MyPoint 
{
    public int X; // Value Type
    public int Y; // Value Type
}

public class MyPointWithName 
{
    public int X; // Value Type
    public int Y; // Value Type
    public string Name; // Reference Type
}

24
2018-01-22 10:17



Các loại tham chiếu không thể thay đổi như string tương đương ngữ nghĩa với các giá trị, và việc lưu trữ một tham chiếu đến một đối tượng bất biến thành một trường không đòi hỏi sự phân bổ heap. Sự khác biệt giữa một cấu trúc với các trường công khai tiếp xúc và một đối tượng lớp với các trường công khai được hiển thị là được cung cấp trình tự mã var q=p; p.X=4; q.X=5;, p.X sẽ có giá trị 4 nếu a là loại cấu trúc và 5 nếu đó là loại lớp. Nếu một người muốn có thể sửa đổi thuận tiện các thành viên của loại, người ta nên chọn 'lớp' hoặc 'cấu trúc' dựa trên việc người ta muốn thay đổi q ảnh hưởng p. - supercat
Có, tôi đồng ý các biến tham chiếu sẽ được trên stack nhưng đối tượng nó đề cập sẽ tồn tại trên heap. Mặc dù cấu trúc và các lớp hoạt động khác nhau khi được gán cho một biến khác, nhưng tôi không nghĩ đó là một yếu tố quyết định mạnh mẽ. - Usman Zafar
Các cấu trúc có thể thay đổi và các lớp có thể thay đổi hoạt động hoàn toàn khác nhau; nếu một là đúng, người kia rất có thể sẽ sai. Tôi không chắc chắn hành vi sẽ không phải là một yếu tố quyết định trong việc xác định liệu có nên sử dụng một cấu trúc hay một lớp. - supercat
Tôi nói đó không phải là yếu tố quyết định mạnh mẽ bởi vì thường khi bạn đang tạo ra một lớp hoặc cấu trúc, bạn không chắc nó sẽ được sử dụng như thế nào. Vì vậy, bạn tập trung vào cách mọi thứ có ý nghĩa hơn từ quan điểm thiết kế. Dù sao tôi đã không bao giờ nhìn thấy tại một nơi duy nhất trong thư viện NET, nơi một struct có chứa một biến tham chiếu. - Usman Zafar
Loại cấu trúc ArraySegment<T> gói gọn một T[], luôn là loại lớp. Loại cấu trúc KeyValuePair<TKey,TValue> thường được sử dụng với các loại lớp như các tham số chung. - supercat


Đầu tiên: Các tình huống Interop hoặc khi bạn cần xác định bố cục bộ nhớ

Thứ hai: Khi dữ liệu gần như cùng kích thước với con trỏ tham chiếu.


17
2018-02-06 18:12





Bạn cần sử dụng "cấu trúc" trong các tình huống mà bạn muốn chỉ định rõ bố cục bộ nhớ bằng cách sử dụng StructLayoutAttribute - thường cho PInvoke.

Edit: Comment chỉ ra rằng bạn có thể sử dụng class hoặc struct với StructLayoutAttribute và điều đó chắc chắn đúng. Trong thực tế, bạn thường sẽ sử dụng một struct - nó được cấp phát trên stack so với heap mà có ý nghĩa nếu bạn chỉ cần truyền một đối số cho một cuộc gọi phương thức không được quản lý.


16
2018-02-06 18:09



StructLayoutAttribute có thể được áp dụng cho các cấu trúc hoặc các lớp vì vậy đây không phải là một lý do để sử dụng các cấu trúc. - Stephen Martin
Tại sao nó có ý nghĩa nếu bạn chỉ là đi qua một đối số cho một cuộc gọi phương thức không được quản lý? - Backwards_Dave