Sự khác biệt giữa struct và class trong .NET là gì?


Câu trả lời:


1057

Trong .NET, có hai loại loại, loại tham chiếuloại giá trị .

Cấu trúc là các loại giá trị và các lớp là các loại tham chiếu .

Sự khác biệt chung là một loại tham chiếu sống trên heap và một loại giá trị sống nội tuyến, nghĩa là, bất cứ nơi nào nó là biến hoặc trường của bạn được xác định.

Một biến chứa một loại giá trị chứa toàn bộ giá trị loại giá trị. Đối với một cấu trúc, điều đó có nghĩa là biến chứa toàn bộ cấu trúc, với tất cả các trường của nó.

Một biến chứa kiểu tham chiếu chứa con trỏ hoặc tham chiếu đến một nơi khác trong bộ nhớ có giá trị thực tế nằm trong đó.

Điều này có một lợi ích, để bắt đầu với:

  • các loại giá trị luôn chứa một giá trị
  • các loại tham chiếu có thể chứa một giá trị null , nghĩa là chúng không tham chiếu bất cứ thứ gì vào lúc này

Trong nội bộ, các kiểu tham chiếu được triển khai như các con trỏ và biết rằng, và biết cách gán biến hoạt động, có các mẫu hành vi khác:

  • sao chép nội dung của biến loại giá trị sang biến khác, sao chép toàn bộ nội dung vào biến mới, làm cho hai nội dung trở nên khác biệt. Nói cách khác, sau khi sao chép, thay đổi thành một sẽ không ảnh hưởng đến cái khác
  • sao chép nội dung của biến loại tham chiếu vào biến khác, sao chép tham chiếu, có nghĩa là bạn hiện có hai tham chiếu đến cùng một nơi lưu trữ dữ liệu thực tế khác. Nói cách khác, sau khi sao chép, việc thay đổi dữ liệu trong một tham chiếu cũng sẽ ảnh hưởng đến người khác, nhưng chỉ vì bạn thực sự chỉ nhìn vào cùng một dữ liệu ở cả hai nơi

Khi bạn khai báo các biến hoặc các trường, đây là cách hai loại khác nhau:

  • biến: loại giá trị sống trên ngăn xếp, loại tham chiếu sống trên ngăn xếp như một con trỏ tới một nơi nào đó trong bộ nhớ heap nơi bộ nhớ thực sự sống (mặc dù lưu ý loạt bài viết của Eric Lipperts: Stack là một chi tiết triển khai .)
  • class / struct-field: kiểu giá trị sống hoàn toàn bên trong kiểu, kiểu tham chiếu sống bên trong kiểu như một con trỏ tới một nơi nào đó trong bộ nhớ heap nơi bộ nhớ thực sống.

43
Vì lợi ích của sự hoàn chỉnh đầy đủ, tôi nên đề cập rằng Eric Lippert đã nói rằng ngăn xếp là một chi tiết triển khai , bất cứ khi nào tôi đề cập đến ngăn xếp ở trên, hãy lưu ý đến bài đăng của Eric.
Lasse V. Karlsen

2
Đây có phải là tất cả hợp lệ cho C ++ không?
Koray Tugay

9
một sự khác biệt quan trọng khác là cách sử dụng. Từ MSDN: "các cấu trúc thường được sử dụng để đóng gói một nhóm nhỏ các biến liên quan, chẳng hạn như tọa độ của hình chữ nhật. Các cấu trúc cũng có thể chứa các hàm tạo, hằng, trường, phương thức, thuộc tính, bộ chỉ mục, toán tử, sự kiện và các kiểu lồng nhau, mặc dù nếu một số thành viên là bắt buộc, bạn nên xem xét việc biến loại của bạn thành một lớp thay thế. "
thewpfguy

4
@KorayTugay Không, không.
Phóng to

9
@KorayTugay trong C ++ struct và class hoàn toàn tương đương ngoại trừ một điều - hạn chế truy cập mặc định (lớp có mặc định riêng, struct có công khai)
berkus

207

Một bản tóm tắt ngắn của mỗi:

Chỉ các lớp học:

  • Có thể hỗ trợ thừa kế
  • Là loại tham chiếu (con trỏ)
  • Tham chiếu có thể là null
  • Có chi phí bộ nhớ cho mỗi phiên bản mới

Cấu trúc chỉ:

  • Không thể hỗ trợ thừa kế
  • Là loại giá trị
  • Được truyền theo giá trị (như số nguyên)
  • Không thể có tham chiếu null (trừ khi Nullable được sử dụng)
  • Không có chi phí bộ nhớ cho mỗi phiên bản mới - trừ khi 'đóng hộp'

Cả hai lớp và cấu trúc:

  • Các kiểu dữ liệu hỗn hợp thường được sử dụng để chứa một vài biến có mối quan hệ logic
  • Có thể chứa các phương thức và sự kiện
  • Có thể hỗ trợ giao diện

16
Có một số phần của câu trả lời này không hoàn toàn đúng. Các lớp học không phải lúc nào cũng đi theo đống, và các cấu trúc không phải lúc nào cũng đi theo chồng. Các ngoại lệ hiện tại bao gồm các trường cấu trúc trên một lớp, các biến được bắt trong các phương thức ẩn danh và các biểu thức lambda, các khối lặp và các giá trị được đóng hộp đã được đề cập. Nhưng stack vs heap phân bổ là một chi tiết thực hiện và có thể thay đổi. Eric lippart thảo luận về điều này ở đây . Tôi đã hạ cấp, nhưng sẽ vui vẻ xóa nó nếu bạn cập nhật.
Simon P Stevens

1
struct không hỗ trợ kế thừa từ các lớp / lớp khác, nhưng bạn CÓ THỂ triển khai giao diện trên struct.
thewpfguy

2
Bạn có thể muốn làm rõ ý của bạn khi bạn tuyên bố rằng cấu trúc "Không có chi phí bộ nhớ cho mỗi phiên bản mới" . Giải thích đầu tiên của tôi là bạn đã tuyên bố - rõ ràng là vô lý - rằng các cấu trúc sử dụng bộ nhớ bằng không. Sau đó, tôi nghĩ rằng có lẽ bạn đang cố gắng nói rằng một cấu trúc, không giống như một lớp, đòi hỏi chính xác nhiều bộ nhớ bằng tổng các trường thành viên của nó, và không còn nữa. Nhưng sau đó tôi đã tìm kiếm c# struct memory overheadvà tìm thấy câu trả lời này của Hans Passant nói rằng không, đó cũng không phải là trường hợp. Vậy điều gì làm bạn nghĩa là gì?
Đánh dấu Amery

4
@MarkAmery Tôi có phản ứng ban đầu giống như bạn id với biểu thức "không có phí bộ nhớ", nhưng tôi nghĩ rằng OP đang đề cập đến thực tế là các phiên bản của classbộ nhớ được quản lý (được xử lý bởi trình thu gom rác), trong khi các trường hợp structkhông phải là .
Hutch

1
"Struct Được truyền theo giá trị (như số nguyên)" là sai: tất cả các biến được truyền theo giá trị, cũng là loại tham chiếu. Nếu bạn muốn chuyển một biến bằng tham chiếu, bạn phải sử dụng từ khóa "ref". jonskeet.uk/csharp/parameter.html#ref
Marco Staffoli

41

Trong .NET, các khai báo struct và class phân biệt giữa các loại tham chiếu và các loại giá trị.

Khi bạn vượt qua vòng một loại tham chiếu, chỉ có một loại thực sự được lưu trữ. Tất cả các mã truy cập thể hiện đang truy cập cùng một mã.

Khi bạn vượt qua vòng một loại giá trị, mỗi loại là một bản sao. Tất cả các mã đang làm việc trên bản sao của chính nó.

Điều này có thể được hiển thị với một ví dụ:

struct MyStruct 
{
    string MyProperty { get; set; }
}

void ChangeMyStruct(MyStruct input) 
{ 
   input.MyProperty = "new value";
}

...

// Create value type
MyStruct testStruct = new MyStruct { MyProperty = "initial value" }; 

ChangeMyStruct(testStruct);

// Value of testStruct.MyProperty is still "initial value"
// - the method changed a new copy of the structure.

Đối với một lớp học, điều này sẽ khác

class MyClass 
{
    string MyProperty { get; set; }
}

void ChangeMyClass(MyClass input) 
{ 
   input.MyProperty = "new value";
}

...

// Create reference type
MyClass testClass = new MyClass { MyProperty = "initial value" };

ChangeMyClass(testClass);

// Value of testClass.MyProperty is now "new value" 
// - the method changed the instance passed.

Các lớp có thể không có gì - tham chiếu có thể trỏ đến null.

Cấu trúc là giá trị thực tế - chúng có thể trống nhưng không bao giờ rỗng. Vì lý do này, các cấu trúc luôn có một hàm tạo mặc định không có tham số - chúng cần một 'giá trị bắt đầu'.


@ T.Todua yeah, có những câu trả lời tốt hơn ở trên, mà tôi đã bỏ phiếu và chọn làm câu trả lời sau khi cung cấp câu trả lời này - đây là từ phiên bản beta đầu tiên của SO khi chúng tôi vẫn đang tìm ra quy tắc.
Keith

1
Tôi không biết nếu bạn hiểu chính xác tôi, tôi thực sự đã nâng cao / chấp nhận câu trả lời của bạn (trái ngược với câu trả lời ở trên), bởi vì bạn có những ví dụ tốt (không chỉ giải thích lý thuyết, trái ngược với câu trả lời trên, chỉ có giải thích lý thuyết mà không có ví dụ ).
T.Todua

24

Sự khác biệt giữa Structs và Classes:

  • Cấu trúc là loại giá trị trong khi Lớp là loại tham chiếu .
  • Các cấu trúc được lưu trữ trên ngăn xếp trong khi các Lớp được lưu trữ trên heap .
  • Các loại giá trị giữ giá trị của chúng trong bộ nhớ nơi chúng được khai báo, nhưng loại tham chiếu giữ tham chiếu đến bộ nhớ đối tượng.
  • Các loại giá trị bị hủy ngay lập tức sau khi phạm vi bị mất trong khi loại tham chiếu chỉ hủy biến sau khi phạm vi bị mất. Đối tượng sau đó bị phá hủy bởi người thu gom rác.
  • Khi bạn sao chép struct vào một cấu trúc khác, một bản sao mới của cấu trúc đó được tạo ra được sửa đổi của một cấu trúc sẽ không ảnh hưởng đến giá trị của cấu trúc kia.
  • Khi bạn sao chép một lớp vào một lớp khác, nó chỉ sao chép biến tham chiếu.
  • Cả hai biến tham chiếu đều trỏ đến cùng một đối tượng trên heap. Thay đổi thành một biến sẽ ảnh hưởng đến biến tham chiếu khác.
  • Các cấu trúc không thể có các hàm hủy , nhưng các lớp có thể có các hàm hủy.
  • Các cấu trúc không thể có các hàm tạo không tham số rõ ràng trong khi một lớp có thể không hỗ trợ kế thừa, nhưng các lớp thì có. Cả hai đều hỗ trợ kế thừa từ một giao diện.
  • Cấu trúc được niêm phong loại .

21

Từ lựa chọn của Microsoft giữa Class và Struct ...

Theo nguyên tắc thông thường, phần lớn các loại trong khung nên là các lớp. Tuy nhiên, có một số tình huống trong đó các đặc tính của một loại giá trị làm cho việc sử dụng các cấu trúc phù hợp hơn.

KIỂM TRA một cấu trúc thay vì một lớp:

  • Nếu các thể hiện của loại nhỏ và thường tồn tại trong thời gian ngắn hoặc thường được nhúng trong các đối tượng khác.

X TRÁNH một cấu trúc trừ khi loại có tất cả các đặc điểm sau:

  • Nó đại 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 (int, double, v.v.).
  • Nó có kích thước cá thể dưới 16 byte.
  • Nó là bất biến. (không thể thay đổi)
  • Nó sẽ không phải được đóng hộp thường xuyên.

19

Ngoài tất cả các khác biệt được mô tả trong các câu trả lời khác:

  1. Các cấu trúc không thể có một hàm tạo không tham số rõ ràng trong khi một lớp có thể
  2. Cấu trúc không thể có hàm hủy , trong khi một lớp có thể
  3. Các cấu trúc không thể kế thừa từ một cấu trúc hoặc lớp khác trong khi một lớp có thể kế thừa từ một lớp khác. (Cả cấu trúc và lớp có thể thực hiện từ một giao diện.)

Nếu bạn đang theo dõi một video giải thích tất cả sự khác biệt, bạn có thể xem Phần 29 - Hướng dẫn C # - Sự khác biệt giữa các lớp và cấu trúc trong C # .


4
Quan trọng hơn nhiều so với thực tế là các ngôn ngữ .net thường sẽ không cho phép một cấu trúc xác định hàm tạo không tham số (quyết định có hay không cho phép nó được tạo bởi trình biên dịch ngôn ngữ) là thực tế rằng các cấu trúc có thể tồn tại và bị lộ đến thế giới bên ngoài mà không có bất kỳ loại hàm tạo nào được chạy (ngay cả khi hàm tạo không tham số được xác định). Lý do. Các ngôn ngữ .net thường cấm các hàm tạo không tham số cho các cấu trúc là để tránh sự nhầm lẫn dẫn đến việc các hàm tạo như vậy đôi khi được chạy và đôi khi không.
supercat

15

Thể hiện của các lớp được lưu trữ trên heap được quản lý. Tất cả các biến 'chứa' một thể hiện chỉ đơn giản là một tham chiếu đến thể hiện trên heap. Truyền một đối tượng cho một phương thức dẫn đến một bản sao của tham chiếu được truyền, chứ không phải chính đối tượng đó.

Các cấu trúc (về mặt kỹ thuật, các loại giá trị) được lưu trữ bất cứ nơi nào chúng được sử dụng, giống như một kiểu nguyên thủy. Các nội dung có thể được sao chép bởi bộ thực thi bất cứ lúc nào và không cần gọi một hàm tạo sao chép tùy chỉnh. Truyền một loại giá trị cho một phương thức liên quan đến việc sao chép toàn bộ giá trị, một lần nữa mà không cần gọi bất kỳ mã tùy chỉnh nào.

Sự khác biệt được làm cho tốt hơn bởi các tên C ++ / CLI: "lớp ref" là một lớp như được mô tả trước, "lớp giá trị" là một lớp như được mô tả thứ hai. Các từ khóa "lớp" và "struct" được sử dụng bởi C # đơn giản là thứ phải học.


11
+------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------+
|                        |                                                Struct                                                |                                               Class                                               |
+------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------+
| Type                   | Value-type                                                                                           | Reference-type                                                                                    |
| Where                  | On stack / Inline in containing type                                                                 | On Heap                                                                                           |
| Deallocation           | Stack unwinds / containing type gets deallocated                                                     | Garbage Collected                                                                                 |
| Arrays                 | Inline, elements are the actual instances of the value type                                          | Out of line, elements are just references to instances of the reference type residing on the heap |
| Aldel Cost             | Cheap allocation-deallocation                                                                        | Expensive allocation-deallocation                                                                 |
| Memory usage           | Boxed when cast to a reference type or one of the interfaces they implement,                         | No boxing-unboxing                                                                                |
|                        | Unboxed when cast back to value type                                                                 |                                                                                                   |
|                        | (Negative impact because boxes are objects that are allocated on the heap and are garbage-collected) |                                                                                                   |
| Assignments            | Copy entire data                                                                                     | Copy the reference                                                                                |
| Change to an instance  | Does not affect any of its copies                                                                    | Affect all references pointing to the instance                                                    |
| Mutability             | Should be immutable                                                                                  | Mutable                                                                                           |
| Population             | In some situations                                                                                   | Majority of types in a framework should be classes                                                |
| Lifetime               | Short-lived                                                                                          | Long-lived                                                                                        |
| Destructor             | Cannot have                                                                                          | Can have                                                                                          |
| Inheritance            | Only from an interface                                                                               | Full support                                                                                      |
| Polymorphism           | No                                                                                                   | Yes                                                                                               |
| Sealed                 | Yes                                                                                                  | When have sealed keyword                                                                          |
| Constructor            | Can not have explicit parameterless constructors                                                     | Any constructor                                                                                   |
| Null-assignments       | When marked with nullable question mark                                                              | Yes (+ When marked with nullable question mark in C# 8+)                                          |
| Abstract               | No                                                                                                   | When have abstract keyword                                                                        |
| Member Access Modifiers| public, private, internal                                                                            | public, protected, internal, protected internal, private protected                                |
+------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------+

1
Điều này thực sự khá lộng lẫy: tóm tắt và nhiều thông tin. Xin vui lòng chỉ nhớ đọc bằng chứng câu trả lời của bạn ít nhất một lần - bạn đã hoán đổi các giải thích về cấu trúc và lớp trong một số hàng, cũng có một số lỗi chính tả.
Robert Kopeć

1
@ensisNoctis Xin lỗi vì những sai lầm đó và cảm ơn vì đã chỉnh sửa. Tôi nên đọc lại câu trả lời của mình
0xaryan

8

Cấu trúc so với lớp

Cấu trúc là một loại giá trị vì vậy nó được lưu trữ trên ngăn xếp, nhưng một lớp là một kiểu tham chiếu và được lưu trữ trên heap.

Một cấu trúc không hỗ trợ kế thừa và đa hình, nhưng một lớp hỗ trợ cả hai.

Theo mặc định, tất cả các thành viên cấu trúc là công khai nhưng các thành viên lớp theo mặc định là riêng tư.

Vì cấu trúc là một loại giá trị, chúng ta không thể gán null cho một đối tượng struct, nhưng nó không phải là trường hợp của một lớp.


5
Về "tất cả các thành viên cấu trúc là công khai": Nếu tôi không nhầm, điều đó không chính xác. "Mức truy cập cho các thành viên lớp và thành viên cấu trúc, bao gồm các lớp và cấu trúc lồng nhau, theo mặc định là riêng tư." msdn.microsoft.com/en-us/l Library / ms173121.aspx
Nate Cook

8

Để thêm vào các câu trả lời khác, có một sự khác biệt cơ bản đáng chú ý và đó là cách dữ liệu được lưu trữ trong các mảng vì điều này có thể có ảnh hưởng lớn đến hiệu suất.

  • Với một struct, mảng chứa thể hiện của struct
  • Với một lớp, mảng chứa một con trỏ tới một thể hiện của lớp ở nơi khác trong bộ nhớ

Vì vậy, một loạt các cấu trúc trông như thế này trong bộ nhớ

[struct][struct][struct][struct][struct][struct][struct][struct]

Trong khi đó một mảng các lớp trông như thế này

[pointer][pointer][pointer][pointer][pointer][pointer][pointer][pointer]

Với một mảng các lớp, các giá trị bạn quan tâm không được lưu trữ trong mảng, mà ở các nơi khác trong bộ nhớ.

Đối với phần lớn các ứng dụng, sự khác biệt này không thực sự quan trọng, tuy nhiên, trong mã hiệu suất cao, điều này sẽ ảnh hưởng đến vị trí dữ liệu trong bộ nhớ và có ảnh hưởng lớn đến hiệu suất của bộ đệm CPU. Sử dụng các lớp khi bạn có thể / nên sử dụng các cấu trúc sẽ tăng ồ ạt số lượng bộ nhớ cache trên CPU.

Điều chậm nhất mà CPU hiện đại không làm hỏng số, đó là tìm nạp dữ liệu từ bộ nhớ và lần truy cập bộ đệm L1 nhanh hơn nhiều lần so với đọc dữ liệu từ RAM.

Đây là một số mã bạn có thể kiểm tra. Trên máy của tôi, việc lặp qua mảng lớp sẽ dài hơn ~ 3 lần so với mảng struct.

    private struct PerformanceStruct
    {
        public int i1;
        public int i2;
    }

    private class PerformanceClass
    {
        public int i1;
        public int i2;
    }

    private static void DoTest()
    {
        var structArray = new PerformanceStruct[100000000];
        var classArray = new PerformanceClass[structArray.Length];

        for (var i = 0; i < structArray.Length; i++)
        {
            structArray[i] = new PerformanceStruct();
            classArray[i] = new PerformanceClass();
        }

        long total = 0;
        var sw = new Stopwatch();
        sw.Start();
        for (var loops = 0; loops < 100; loops++)
        for (var i = 0; i < structArray.Length; i++)
        {
            total += structArray[i].i1 + structArray[i].i2;
        }

        sw.Stop();
        Console.WriteLine($"Struct Time: {sw.ElapsedMilliseconds}");
        sw = new Stopwatch();
        sw.Start();
        for (var loops = 0; loops < 100; loops++)
        for (var i = 0; i < classArray.Length; i++)
        {
            total += classArray[i].i1 + classArray[i].i2;
        }

        Console.WriteLine($"Class Time: {sw.ElapsedMilliseconds}");
    }

-1; "Cấu trúc là các loại giá trị, vì vậy chúng lưu trữ một giá trị, các lớp là các loại tham chiếu, vì vậy chúng tham chiếu một lớp." không rõ ràng và không có ý nghĩa đối với bất kỳ ai chưa hiểu nó từ các câu trả lời khác ở đây và "Với một lớp, lớp chứa sẽ chỉ chứa một con trỏ đến lớp mới trong một vùng nhớ khác." nhầm lẫn các lớp với các trường hợp lớp.
Đánh dấu Amery

@MarkAmery Tôi đã cố gắng làm rõ một chút. Điểm tôi thực sự cố gắng tạo ra là sự khác biệt trong cách thức mảng hoạt động với các loại giá trị và tham chiếu và ảnh hưởng của nó đến hiệu suất. Tôi đã không cố gắng giải thích lại giá trị và loại tham chiếu là gì vì điều này được thực hiện trong nhiều câu trả lời khác.
Will Calderwood

7

Chỉ cần làm cho nó hoàn chỉnh, có một sự khác biệt khác khi sử dụng Equalsphương thức, được kế thừa bởi tất cả các lớp và cấu trúc.

Hãy nói rằng chúng ta có một lớp và một cấu trúc:

class A{
  public int a, b;
}
struct B{
  public int a, b;
}

và trong phương thức Main, chúng ta có 4 đối tượng.

static void Main{
  A c1 = new A(), c2 = new A();
  c1.a = c1.b = c2.a = c2.b = 1;
  B s1 = new B(), s2 = new B();
  s1.a = s1.b = s2.a = s2.b = 1;
}

Sau đó:

s1.Equals(s2) // true
s1.Equals(c1) // false
c1.Equals(c2) // false
c1 == c2 // false

Vì vậy , các cấu trúc phù hợp với các đối tượng giống như số, như các điểm (lưu tọa độ x và y). Và các lớp học phù hợp cho những người khác. Ngay cả khi 2 người có cùng tên, chiều cao, cân nặng ..., họ vẫn là 2 người.


6

Vâng, đối với người mới bắt đầu, một cấu trúc được truyền bằng giá trị chứ không phải bằng tham chiếu. Các cấu trúc tốt cho các cấu trúc dữ liệu tương đối đơn giản, trong khi các lớp có tính linh hoạt hơn rất nhiều từ quan điểm kiến ​​trúc thông qua tính đa hình và kế thừa.

Những người khác có thể có thể cung cấp cho bạn nhiều chi tiết hơn tôi, nhưng tôi sử dụng các cấu trúc khi cấu trúc mà tôi sẽ đơn giản.


4

Bên cạnh sự khác biệt cơ bản của trình xác định truy cập và một vài điều được đề cập ở trên, tôi muốn thêm một số khác biệt chính bao gồm một vài trong số các đề cập ở trên với một mẫu mã có đầu ra, điều này sẽ cho ý tưởng rõ ràng hơn về tham chiếu và giá trị

Cấu trúc:

  • Là các loại giá trị và không yêu cầu phân bổ heap.
  • Cấp phát bộ nhớ là khác nhau và được lưu trữ trong ngăn xếp
  • Hữu ích cho các cấu trúc dữ liệu nhỏ
  • Ảnh hưởng đến hiệu suất, khi chúng ta truyền giá trị cho phương thức, chúng ta chuyển toàn bộ cấu trúc dữ liệu và tất cả được truyền vào ngăn xếp.
  • Trình xây dựng chỉ đơn giản trả về giá trị cấu trúc (thường ở vị trí tạm thời trên ngăn xếp) và sau đó giá trị này được sao chép khi cần thiết
  • Mỗi biến có một bản sao riêng của dữ liệu và các thao tác trên một biến này không thể ảnh hưởng đến các biến khác.
  • Không hỗ trợ thừa kế do người dùng chỉ định và họ thừa kế hoàn toàn từ đối tượng loại

Lớp học:

  • Giá trị loại tham chiếu
  • Được lưu trữ trong Heap
  • Lưu trữ một tham chiếu đến một đối tượng được phân bổ động
  • Các nhà xây dựng được gọi với toán tử mới, nhưng điều đó không phân bổ bộ nhớ trên heap
  • Nhiều biến có thể có một tham chiếu đến cùng một đối tượng
  • Có thể các hoạt động trên một biến ảnh hưởng đến đối tượng được tham chiếu bởi biến khác

Mẫu mã

    static void Main(string[] args)
    {
        //Struct
        myStruct objStruct = new myStruct();
        objStruct.x = 10;
        Console.WriteLine("Initial value of Struct Object is: " + objStruct.x);
        Console.WriteLine();
        methodStruct(objStruct);
        Console.WriteLine();
        Console.WriteLine("After Method call value of Struct Object is: " + objStruct.x);
        Console.WriteLine();

        //Class
        myClass objClass = new myClass(10);
        Console.WriteLine("Initial value of Class Object is: " + objClass.x);
        Console.WriteLine();
        methodClass(objClass);
        Console.WriteLine();
        Console.WriteLine("After Method call value of Class Object is: " + objClass.x);
        Console.Read();
    }
    static void methodStruct(myStruct newStruct)
    {
        newStruct.x = 20;
        Console.WriteLine("Inside Struct Method");
        Console.WriteLine("Inside Method value of Struct Object is: " + newStruct.x);
    }
    static void methodClass(myClass newClass)
    {
        newClass.x = 20;
        Console.WriteLine("Inside Class Method");
        Console.WriteLine("Inside Method value of Class Object is: " + newClass.x);
    }
    public struct myStruct
    {
        public int x;
        public myStruct(int xCons)
        {
            this.x = xCons;
        }
    }
    public class myClass
    {
        public int x;
        public myClass(int xCons)
        {
            this.x = xCons;
        }
    }

Đầu ra

Giá trị ban đầu của đối tượng Struct là: 10

Phương thức bên trong Giá trị phương thức bên trong Giá trị phương thức của đối tượng Struct là: 20

Giá trị cuộc gọi Phương thức của đối tượng Struct là: 10

Giá trị ban đầu của Class Object là: 10

Phương thức bên trong Giá trị phương thức bên trong của Đối tượng lớp là: 20

Giá trị cuộc gọi Phương thức của Đối tượng lớp là: 20

Ở đây bạn có thể thấy rõ sự khác biệt giữa cuộc gọi theo giá trị và cuộc gọi theo tham chiếu.


4
  1. Các sự kiện được khai báo trong một lớp có quyền truy cập + = và - = của chúng tự động bị khóa thông qua khóa (điều này) để làm cho chúng an toàn theo luồng (các sự kiện tĩnh được khóa trên kiểu của lớp). Các sự kiện được khai báo trong một cấu trúc không có quyền truy cập + = và - = của chúng tự động bị khóa. Một khóa (cái này) cho một cấu trúc sẽ không hoạt động vì bạn chỉ có thể khóa trên một biểu thức kiểu tham chiếu.

  2. Tạo một cá thể cấu trúc không thể gây ra một bộ sưu tập rác (trừ khi hàm tạo trực tiếp hoặc gián tiếp tạo một thể hiện kiểu tham chiếu) trong khi tạo một thể hiện kiểu tham chiếu có thể gây ra bộ sưu tập rác.

  3. Một cấu trúc luôn có một hàm tạo mặc định công khai tích hợp.

    class DefaultConstructor
    {
        static void Eg()
        {
            Direct     yes = new   Direct(); // Always compiles OK
            InDirect maybe = new InDirect(); // Compiles if constructor exists and is accessible
            //...
        }
    }

    Điều này có nghĩa là một cấu trúc luôn luôn khả thi trong khi một lớp có thể không vì tất cả các hàm tạo của nó có thể là riêng tư.

    class NonInstantiable
    {
        private NonInstantiable() // OK
        {
        }
    }
    
    struct Direct
    {
        private Direct() // Compile-time error
        {
        }
    }
  4. Một cấu trúc không thể có một hàm hủy. Một hàm hủy chỉ là một phần ghi đè của object.Finalize trong ngụy trang và các cấu trúc, là các loại giá trị, không phải là đối tượng thu gom rác.

    struct Direct
    {
        ~Direct() {} // Compile-time error
    }
    class InDirect
    {
        ~InDirect() {} // Compiles OK
    }
    
    And the CIL for ~Indirect() looks like this:
    
    .method family hidebysig virtual instance void
            Finalize() cil managed
    {
      // ...
    } // end of method Indirect::Finalize
  5. Một cấu trúc được ngầm kín, một lớp không.
    Một cấu trúc không thể trừu tượng, một lớp có thể.
    Một struct không thể gọi: base () trong hàm tạo của nó trong khi một lớp không có lớp cơ sở rõ ràng có thể.
    Một cấu trúc không thể mở rộng một lớp khác, một lớp có thể.
    Một cấu trúc không thể khai báo các thành viên được bảo vệ (ví dụ: các trường, các kiểu lồng nhau) một lớp có thể.
    Một cấu trúc không thể khai báo các thành viên hàm trừu tượng, một lớp trừu tượng có thể.
    Một cấu trúc không thể khai báo các thành viên hàm ảo, một lớp có thể.
    Một cấu trúc không thể khai báo các thành viên chức năng niêm phong, một lớp có thể.
    Một cấu trúc không thể khai báo các thành viên hàm ghi đè, một lớp có thể.
    Một ngoại lệ cho quy tắc này là một cấu trúc có thể ghi đè các phương thức ảo của System.Object, viz, Equals () và GetHashCode () và ToString ().


Trong trường hợp nào người ta sẽ sử dụng một sự kiện với một cấu trúc? Tôi có thể tưởng tượng rằng một chương trình được viết rất cẩn thận có thể sử dụng các sự kiện với một cấu trúc theo cách sẽ hoạt động, nhưng chỉ khi cấu trúc đó không bao giờ được sao chép hoặc chuyển qua giá trị, trong trường hợp đó cũng có thể là một lớp.
supercat

@supercat Vâng, một sự kiện không tĩnh trong cấu trúc sẽ rất lạ và nó chỉ hữu ích cho các cấu trúc có thể thay đổi và chính sự kiện đó (nếu là sự kiện "giống như trường") biến cấu trúc thành "có thể thay đổi "Thể loại và cũng giới thiệu một lĩnh vực loại tham chiếu trong cấu trúc. Các sự kiện không tĩnh trong các cấu trúc phải là xấu xa.
Jeppe Stig Nielsen

@JeppeStigNielsen: Mẫu duy nhất tôi có thể thấy nơi có ý nghĩa đối với một cấu trúc sẽ có một sự kiện là nếu mục đích của cấu trúc là giữ một tham chiếu bất biến đến một đối tượng lớp mà nó hoạt động như một proxy. Mặc dù vậy, các sự kiện tự động sẽ hoàn toàn vô dụng trong một kịch bản như vậy; thay vào đó, các sự kiện đăng ký và hủy đăng ký sẽ phải được chuyển tiếp đến lớp đằng sau cấu trúc. Tôi ước .NET có (hoặc có thể xác định) loại cấu trúc 'hộp bộ đệm "với trường loại ẩn ban đầu không có giá trị Object, sẽ giữ tham chiếu đến bản sao được đóng hộp của cấu trúc.
supercat

1
@JeppeStigNielsen: Kết quả tốt hơn các lớp trong nhiều tình huống sử dụng proxy; vấn đề lớn nhất khi sử dụng cấu trúc là trong trường hợp quyền anh kết thúc là cần thiết, nó thường bị trì hoãn đến một vòng lặp bên trong. Nếu có một cách để tránh việc các cấu trúc bị đóng hộp liên tục , chúng sẽ tốt hơn các lớp trong nhiều tình huống sử dụng hơn.
supercat

4

Như đã đề cập trước đây: Các lớp là loại tham chiếu trong khi Structs là loại giá trị với tất cả các hậu quả.

Như một ngón tay cái của Nguyên tắc Thiết kế Khung Quy tắc khuyến nghị sử dụng Structs thay vì các lớp nếu:

  • Nó có kích thước cá thể dưới 16 byte
  • Nó đại 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 (int, double, v.v.)
  • Nó là bất biến
  • Nó sẽ không phải được đóng hộp thường xuyên

3

Có một trường hợp thú vị của câu đố "class vs struct" - tình huống khi bạn cần trả về một số kết quả từ phương thức: chọn sử dụng. Nếu bạn biết câu chuyện ValueTuple - bạn biết rằng ValueTuple (struct) đã được thêm vào vì nó sẽ hiệu quả hơn thì Tuple (class). Nhưng nó có nghĩa gì trong số? Hai bài kiểm tra: một là struct / class có 2 trường, một trường có struct / class có 8 trường (với thứ nguyên nhiều hơn 4 lớp sẽ trở nên hiệu quả hơn sau đó cấu trúc theo thuật ngữ tick bộ xử lý, nhưng tất nhiên cũng nên xem xét tải GC ).

PS Một điểm chuẩn khác cho trường hợp cụ thể 'sturct hoặc class with Collection' là: https://stackoverflow.com/a/45276657/506147

BenchmarkDotNet=v0.10.10, OS=Windows 10 Redstone 2 [1703, Creators Update] (10.0.15063.726)
Processor=Intel Core i5-2500K CPU 3.30GHz (Sandy Bridge), ProcessorCount=4
Frequency=3233540 Hz, Resolution=309.2586 ns, Timer=TSC
.NET Core SDK=2.0.3
  [Host] : .NET Core 2.0.3 (Framework 4.6.25815.02), 64bit RyuJIT
  Clr    : .NET Framework 4.7 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.2115.0
  Core   : .NET Core 2.0.3 (Framework 4.6.25815.02), 64bit RyuJIT


            Method |  Job | Runtime |     Mean |     Error |    StdDev |      Min |      Max |   Median | Rank |  Gen 0 | Allocated |
------------------ |----- |-------- |---------:|----------:|----------:|---------:|---------:|---------:|-----:|-------:|----------:|
  TestStructReturn |  Clr |     Clr | 17.57 ns | 0.1960 ns | 0.1834 ns | 17.25 ns | 17.89 ns | 17.55 ns |    4 | 0.0127 |      40 B |
   TestClassReturn |  Clr |     Clr | 21.93 ns | 0.4554 ns | 0.5244 ns | 21.17 ns | 23.26 ns | 21.86 ns |    5 | 0.0229 |      72 B |
 TestStructReturn8 |  Clr |     Clr | 38.99 ns | 0.8302 ns | 1.4097 ns | 37.36 ns | 42.35 ns | 38.50 ns |    8 | 0.0127 |      40 B |
  TestClassReturn8 |  Clr |     Clr | 23.69 ns | 0.5373 ns | 0.6987 ns | 22.70 ns | 25.24 ns | 23.37 ns |    6 | 0.0305 |      96 B |
  TestStructReturn | Core |    Core | 12.28 ns | 0.1882 ns | 0.1760 ns | 11.92 ns | 12.57 ns | 12.30 ns |    1 | 0.0127 |      40 B |
   TestClassReturn | Core |    Core | 15.33 ns | 0.4343 ns | 0.4063 ns | 14.83 ns | 16.44 ns | 15.31 ns |    2 | 0.0229 |      72 B |
 TestStructReturn8 | Core |    Core | 34.11 ns | 0.7089 ns | 1.4954 ns | 31.52 ns | 36.81 ns | 34.03 ns |    7 | 0.0127 |      40 B |
  TestClassReturn8 | Core |    Core | 17.04 ns | 0.2299 ns | 0.2150 ns | 16.68 ns | 17.41 ns | 16.98 ns |    3 | 0.0305 |      96 B |

Kiểm tra mã:

using System;
using System.Text;
using System.Collections.Generic;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Attributes.Columns;
using BenchmarkDotNet.Attributes.Exporters;
using BenchmarkDotNet.Attributes.Jobs;
using DashboardCode.Routines.Json;

namespace Benchmark
{
    //[Config(typeof(MyManualConfig))]
    [RankColumn, MinColumn, MaxColumn, StdDevColumn, MedianColumn]
    [ClrJob, CoreJob]
    [HtmlExporter, MarkdownExporter]
    [MemoryDiagnoser]
    public class BenchmarkStructOrClass
    {
        static TestStruct testStruct = new TestStruct();
        static TestClass testClass = new TestClass();
        static TestStruct8 testStruct8 = new TestStruct8();
        static TestClass8 testClass8 = new TestClass8();
        [Benchmark]
        public void TestStructReturn()
        {
            testStruct.TestMethod();
        }

        [Benchmark]
        public void TestClassReturn()
        {
            testClass.TestMethod();
        }


        [Benchmark]
        public void TestStructReturn8()
        {
            testStruct8.TestMethod();
        }

        [Benchmark]
        public void TestClassReturn8()
        {
            testClass8.TestMethod();
        }

        public class TestStruct
        {
            public int Number = 5;
            public struct StructType<T>
            {
                public T Instance;
                public List<string> List;
            }

            public int TestMethod()
            {
                var s = Method1(1);
                return s.Instance;
            }

            private StructType<int> Method1(int i)
            {
                return Method2(++i);
            }

            private StructType<int> Method2(int i)
            {
                return Method3(++i);
            }

            private StructType<int> Method3(int i)
            {
                return Method4(++i);
            }

            private StructType<int> Method4(int i)
            {
                var x = new StructType<int>();
                x.List = new List<string>();
                x.Instance = ++i;
                return x;
            }
        }

        public class TestClass
        {
            public int Number = 5;
            public class ClassType<T>
            {
                public T Instance;
                public List<string> List;
            }

            public int TestMethod()
            {
                var s = Method1(1);
                return s.Instance;
            }

            private ClassType<int> Method1(int i)
            {
                return Method2(++i);
            }

            private ClassType<int> Method2(int i)
            {
                return Method3(++i);
            }

            private ClassType<int> Method3(int i)
            {
                return Method4(++i);
            }

            private ClassType<int> Method4(int i)
            {
                var x = new ClassType<int>();
                x.List = new List<string>();
                x.Instance = ++i;
                return x;
            }
        }

        public class TestStruct8
        {
            public int Number = 5;
            public struct StructType<T>
            {
                public T Instance1;
                public T Instance2;
                public T Instance3;
                public T Instance4;
                public T Instance5;
                public T Instance6;
                public T Instance7;
                public List<string> List;
            }

            public int TestMethod()
            {
                var s = Method1(1);
                return s.Instance1;
            }

            private StructType<int> Method1(int i)
            {
                return Method2(++i);
            }

            private StructType<int> Method2(int i)
            {
                return Method3(++i);
            }

            private StructType<int> Method3(int i)
            {
                return Method4(++i);
            }

            private StructType<int> Method4(int i)
            {
                var x = new StructType<int>();
                x.List = new List<string>();
                x.Instance1 = ++i;
                return x;
            }
        }

        public class TestClass8
        {
            public int Number = 5;
            public class ClassType<T>
            {
                public T Instance1;
                public T Instance2;
                public T Instance3;
                public T Instance4;
                public T Instance5;
                public T Instance6;
                public T Instance7;
                public List<string> List;
            }

            public int TestMethod()
            {
                var s = Method1(1);
                return s.Instance1;
            }

            private ClassType<int> Method1(int i)
            {
                return Method2(++i);
            }

            private ClassType<int> Method2(int i)
            {
                return Method3(++i);
            }

            private ClassType<int> Method3(int i)
            {
                return Method4(++i);
            }

            private ClassType<int> Method4(int i)
            {
                var x = new ClassType<int>();
                x.List = new List<string>();
                x.Instance1 = ++i;
                return x;
            }
        }
    }
}

2

Cấu trúc là giá trị thực - chúng có thể trống nhưng không bao giờ rỗng

Điều này là đúng, tuy nhiên cũng lưu ý rằng các cấu trúc .NET 2 hỗ trợ phiên bản Nullable và C # cung cấp một số đường cú pháp để giúp sử dụng dễ dàng hơn.

int? value = null;
value  = 1;

1
Xin lưu ý rằng đây chỉ là đường cú pháp có nội dung 'Không thể <int> value = null;'
Erik van Brakel

@ErikvanBrakel Đó không chỉ là đường cú pháp. Các quy tắc quyền anh khác nhau có nghĩa là (object)(default(int?)) == nullbạn không thể làm gì với bất kỳ loại giá trị nào khác, bởi vì có nhiều thứ hơn là đường đang diễn ra ở đây. Đường duy nhất là int?cho Nullable<int>.
Jon Hanna

-1; điều này không giải quyết được câu hỏi về sự khác biệt giữa các cấu trúc và các lớp là gì, và do đó, nó nên là một nhận xét về câu trả lời mà bạn đang trả lời, không phải là một câu trả lời riêng biệt. (Mặc dù có lẽ các chỉ tiêu trang web đã khác vào tháng 8 năm 2008!)
Mark Amery

1

Mỗi biến hoặc trường của loại giá trị nguyên thủy hoặc loại cấu trúc chứa một thể hiện duy nhất của loại đó, bao gồm tất cả các trường của nó (công khai và riêng tư). Ngược lại, các biến hoặc trường của các loại tham chiếu có thể giữ null hoặc có thể tham chiếu đến một đối tượng, được lưu trữ ở nơi khác, mà bất kỳ số lượng các tham chiếu khác cũng có thể tồn tại. Các trường của một cấu trúc sẽ được lưu trữ ở cùng một nơi với biến hoặc trường của loại cấu trúc đó, có thể nằm trên ngăn xếp hoặc có thể là một phần của đối tượng heap khác.

Tạo một biến hoặc trường của một loại giá trị nguyên thủy sẽ tạo ra nó với một giá trị mặc định; tạo một biến hoặc trường của kiểu cấu trúc sẽ tạo một thể hiện mới, tạo tất cả các trường trong đó theo cách mặc định. Tạo một ví dụ mới của một loại tài liệu tham khảo sẽ bắt đầu bằng cách tạo ra tất cả các lĩnh vực trong đó theo cách mặc định, và sau đó chạy mã bổ sung tùy chọn tùy thuộc vào loại.

Sao chép một biến hoặc trường thuộc kiểu nguyên thủy sang biến khác sẽ sao chép giá trị. Sao chép một biến hoặc trường kiểu cấu trúc sang loại khác sẽ sao chép tất cả các trường (công khai và riêng tư) của thể hiện trước sang thể hiện sau. Sao chép một biến hoặc trường thuộc loại tham chiếu sang biến khác sẽ khiến biến sau tham chiếu cùng thể hiện với biến trước (nếu có).

Điều quan trọng cần lưu ý là trong một số ngôn ngữ như C ++, hành vi ngữ nghĩa của một loại độc lập với cách lưu trữ, nhưng điều đó không đúng với .NET. Nếu một kiểu thực hiện ngữ nghĩa giá trị có thể thay đổi, việc sao chép một biến của loại đó sang một bản sao khác thuộc tính của trường hợp thứ nhất sang trường hợp khác, được gọi bởi lần thứ hai và sử dụng một thành viên của lần thứ hai để thay đổi nó sẽ khiến trường hợp thứ hai bị thay đổi , nhưng không phải là người đầu tiên. Nếu một kiểu thực hiện ngữ nghĩa tham chiếu có thể thay đổi, sao chép một biến này sang một biến khác và sử dụng một thành viên của biến thứ hai để biến đổi đối tượng sẽ ảnh hưởng đến đối tượng được gọi bởi biến thứ nhất; các loại với ngữ nghĩa bất biến không cho phép đột biến, do đó, không quan trọng về mặt ngữ nghĩa cho dù việc sao chép tạo ra một thể hiện mới hay tạo một tham chiếu khác cho lần đầu tiên.

Trong .NET, các loại giá trị có thể thực hiện bất kỳ ngữ nghĩa nào ở trên, với điều kiện là tất cả các trường của chúng có thể thực hiện tương tự. Tuy nhiên, một loại tham chiếu chỉ có thể thực hiện ngữ nghĩa tham chiếu có thể thay đổi hoặc ngữ nghĩa bất biến; các loại giá trị với các trường của các loại tham chiếu có thể thay đổi được giới hạn trong việc triển khai ngữ nghĩa tham chiếu có thể thay đổi hoặc ngữ nghĩa lai kỳ lạ.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.