it-swarm-vi.tech

Khi nào tôi nên sử dụng struct thay vì một lớp?

MSDN nói rằng bạn nên sử dụng cấu trúc khi bạn cần các vật nhẹ. Có kịch bản nào khác khi một cấu trúc được ưa thích hơn một lớp không?

Một số người có thể đã quên rằng:

  1. structs có thể có các phương thức.
  2. cấu trúc không thể được kế thừa.

Tôi hiểu sự khác biệt về kỹ thuật giữa các cấu trúc và các lớp, tôi chỉ không cảm thấy tốt cho khi để sử dụng một cấu trúc.

295
Esteban Araya

MSDN có câu trả lời: Lựa chọn giữa các lớp và cấu trúc .

Về cơ bản, trang đó cung cấp cho bạn một danh sách kiểm tra gồm 4 mục và nói sẽ sử dụng một lớp trừ khi loại của bạn đáp ứng tất cả các tiêu chí.

Không xác định 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 (số nguyên, gấp đôi, v.v.).
  • Nó có kích thước cá thể nhỏ hơn 16 byte.
  • Nó là bất biến.
  • Nó sẽ không phải được đóng hộp thường xuyên.
293
OwenP

Tôi ngạc nhiên khi tôi chưa đọc bất kỳ câu trả lời nào trước đây, điều mà tôi cho là khía cạnh quan trọng nhất:

Tôi sử dụng structs khi tôi muốn một loại không có danh tính. Ví dụ: điểm 3D:

public struct ThreeDimensionalPoint
{
    public readonly int X, Y, Z;
    public ThreeDimensionalPoint(int x, int y, int z)
    {
        this.X = x;
        this.Y = y;
        this.Z = z;
    }

    public override string ToString()
    {
        return "(X=" + this.X + ", Y=" + this.Y + ", Z=" + this.Z + ")";
    }

    public override int GetHashCode()
    {
        return (this.X + 2) ^ (this.Y + 2) ^ (this.Z + 2);
    }

    public override bool Equals(object obj)
    {
        if (!(obj is ThreeDimensionalPoint))
            return false;
        ThreeDimensionalPoint other = (ThreeDimensionalPoint)obj;
        return this == other;
    }

    public static bool operator ==(ThreeDimensionalPoint p1, ThreeDimensionalPoint p2)
    {
        return p1.X == p2.X && p1.Y == p2.Y && p1.Z == p2.Z;
    }

    public static bool operator !=(ThreeDimensionalPoint p1, ThreeDimensionalPoint p2)
    {
        return !(p1 == p2);
    }
}

Nếu bạn có hai phiên bản của cấu trúc này, bạn không quan tâm nếu chúng là một phần dữ liệu duy nhất trong bộ nhớ hoặc hai. Bạn chỉ cần quan tâm đến (các) giá trị họ nắm giữ.

53
Andrei Rînea

Bill Wagner có một chương về điều này trong cuốn sách "hiệu quả c #" của mình ( http://www <azon.com/Effective-Specific-Ways-Improve-Your/dp/032124566 ). Ông kết luận bằng cách sử dụng nguyên tắc sau:

  1. Là khả năng đáp ứng chính của lưu trữ dữ liệu loại?
  2. Là giao diện công cộng của nó được xác định hoàn toàn bởi các thuộc tính truy cập hoặc sửa đổi các thành viên dữ liệu của nó?
  3. Bạn có chắc chắn loại của bạn sẽ không bao giờ có các lớp con?
  4. Bạn có chắc chắn loại của bạn sẽ không bao giờ được điều trị đa hình?

Nếu bạn trả lời 'có' cho cả 4 câu hỏi: hãy sử dụng cấu trúc. Nếu không, sử dụng một lớp.

27
Bart Gijssens

Sử dụng một cấu trúc khi bạn muốn ngữ nghĩa loại giá trị thay vì kiểu tham chiếu. Cấu trúc được sao chép theo giá trị vì vậy hãy cẩn thận!

Cũng xem các câu hỏi trước, ví dụ:.

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

15
Simon Steele

Tôi sẽ sử dụng cấu trúc khi:

  1. một đối tượng được cho là chỉ đọc (mỗi khi bạn vượt qua/gán một cấu trúc thì nó sẽ được sao chép). Các đối tượng chỉ đọc là tuyệt vời khi xử lý đa luồng vì chúng không yêu cầu khóa trong hầu hết các trường hợp.

  2. một đối tượng là nhỏ và ngắn hạn. Trong trường hợp như vậy, rất có khả năng đối tượng sẽ được phân bổ trên stack, hiệu quả hơn nhiều so với việc đặt nó vào heap được quản lý. Hơn nữa bộ nhớ được phân bổ bởi đối tượng sẽ được giải phóng ngay khi nó vượt ra ngoài phạm vi của nó. Nói cách khác, nó ít hoạt động hơn đối với Garbage Collector và bộ nhớ được sử dụng hiệu quả hơn.

11
Pawel Pabich

Sử dụng một lớp nếu:

  • Bản sắc của nó rất quan trọng. Các cấu trúc được sao chép ngầm khi được truyền bởi giá trị vào một phương thức.
  • Nó sẽ có một dấu chân bộ nhớ lớn.
  • Các lĩnh vực của nó cần khởi tạo.
  • Bạn cần kế thừa từ một lớp cơ sở.
  • Bạn cần hành vi đa hình;

Sử dụng cấu trúc nếu:

  • Nó sẽ hoạt động như một kiểu nguyên thủy (int, long, byte, v.v.).
  • Nó phải có một dấu chân bộ nhớ nhỏ.
  • Bạn đang gọi một phương thức P/Gọi yêu cầu một cấu trúc được truyền vào bằng giá trị.
  • Bạn cần giảm tác động của việc thu gom rác đến hiệu suất của ứng dụng.
  • Các trường của nó chỉ cần được khởi tạo với các giá trị mặc định của chúng. Giá trị này sẽ bằng 0 đối với các loại số, sai đối với các loại Boolean và null đối với các loại tham chiếu. [.__.]
    • Lưu ý rằng trong các cấu trúc C # 6.0 có thể có một hàm tạo mặc định có thể được sử dụng để khởi tạo các trường struct struct thành các giá trị không phá hủy.
  • Bạn không cần phải kế thừa từ một lớp cơ sở (ngoài ValueType, từ đó tất cả các cấu trúc kế thừa).
  • Bạn không cần hành vi đa hình.
10

Tôi đã luôn sử dụng một cấu trúc khi tôi muốn nhóm lại một vài giá trị để chuyển mọi thứ trở lại từ một cuộc gọi phương thức, nhưng tôi sẽ không cần sử dụng nó cho bất cứ điều gì sau khi tôi đã đọc các giá trị đó. Chỉ là một cách để giữ cho mọi thứ sạch sẽ. Tôi có xu hướng xem mọi thứ trong một cấu trúc là "vứt bỏ" và những thứ trong một lớp là hữu ích và "chức năng" hơn

5
Ryan Skarin

Nếu một thực thể sẽ không thay đổi, câu hỏi về việc nên sử dụng một cấu trúc hay một lớp nói chung sẽ là một trong những hiệu suất hơn là ngữ nghĩa. Trên hệ thống 32/64 bit, các tham chiếu lớp yêu cầu 4/8 byte để lưu trữ, bất kể lượng thông tin trong lớp; sao chép một tham chiếu lớp sẽ yêu cầu sao chép 4/8 byte. Mặt khác, mọi khác biệt cá thể lớp sẽ có 8/16 byte chi phí ngoài thông tin chứa và chi phí bộ nhớ của các tham chiếu đến nó. Giả sử một người muốn một mảng gồm 500 thực thể, mỗi thực thể chứa bốn số nguyên 32 bit. Nếu thực thể là một kiểu cấu trúc, mảng sẽ yêu cầu 8.000 byte bất kể tất cả 500 thực thể đều giống hệt nhau, tất cả đều khác nhau hoặc ở đâu đó giữa. Nếu thực thể là một loại lớp, mảng 500 tham chiếu sẽ mất 4.000 byte. Nếu tất cả các tham chiếu đều trỏ đến các đối tượng khác nhau, thì các đối tượng sẽ yêu cầu thêm 24 byte mỗi đối tượng (12.000 byte cho tất cả 500), tổng cộng 16.000 byte - gấp đôi chi phí lưu trữ của loại cấu trúc. Mặt khác, trong mã đã tạo một thể hiện đối tượng và sau đó sao chép một tham chiếu đến tất cả 500 vị trí mảng, tổng chi phí sẽ là 24 byte cho thể hiện đó và 4.000 cho mảng - tổng cộng 4.024 byte. Một khoản tiết kiệm lớn. Rất ít tình huống sẽ diễn ra tốt như tình huống cuối cùng, nhưng trong một số trường hợp, có thể sao chép một số tham chiếu đến đủ các vị trí mảng để làm cho việc chia sẻ đó trở nên đáng giá.

Nếu thực thể được coi là có thể thay đổi, câu hỏi về việc sử dụng một lớp hoặc cấu trúc là một cách dễ dàng hơn. Giả sử "Điều" là một cấu trúc hoặc lớp có trường số nguyên gọi là x và người ta thực hiện mã sau:

[.__.] Điều t1, t2; [.__.] ... [.__.] T2 = t1; [.__.] T2.x = 5; [.__.]

Có ai muốn câu lệnh sau ảnh hưởng đến t1.x không?

Nếu Thing là một loại lớp, t1 và t2 sẽ tương đương, có nghĩa là t1.x và t2.x cũng sẽ tương đương. Do đó, câu lệnh thứ hai sẽ ảnh hưởng đến t1.x. Nếu Thing là một loại cấu trúc, t1 và t2 sẽ là các trường hợp khác nhau, có nghĩa là t1.x và t2.x sẽ đề cập đến các số nguyên khác nhau. Do đó, câu lệnh thứ hai sẽ không ảnh hưởng đến t1.x.

Các cấu trúc có thể thay đổi và các lớp có thể thay đổi có các hành vi khác nhau về cơ bản, mặc dù .net có một số điểm kỳ quặc trong việc xử lý các đột biến cấu trúc. Nếu ai đó muốn hành vi loại giá trị (có nghĩa là "t2 = t1" sẽ sao chép dữ liệu từ t1 sang t2 trong khi để t1 và t2 làm các trường hợp riêng biệt) và nếu một người có thể sống với các quirks trong cách xử lý các loại giá trị của .net, hãy sử dụng một cấu trúc. Nếu ai đó muốn ngữ nghĩa loại giá trị nhưng các quirks của .net sẽ dẫn đến ngữ nghĩa loại giá trị bị hỏng trong ứng dụng của một người, hãy sử dụng một lớp và lẩm bẩm.

4
supercat

Ngoài ra các câu trả lời tuyệt vời ở trên:

Cấu trúc là các loại giá trị.

Chúng không bao giờ có thể được đặt thành Không có gì.

Đặt cấu trúc = Không có gì, sẽ đặt tất cả các loại giá trị của nó thành giá trị mặc định của chúng.

3
George Filippakos

khi bạn không thực sự cần hành vi, nhưng bạn cần nhiều cấu trúc hơn một mảng hoặc từ điển đơn giản.

Theo dõi Đây là cách tôi nghĩ về các cấu trúc nói chung. Tôi biết họ có thể có phương pháp, nhưng tôi thích giữ sự phân biệt tinh thần chung đó.

2
Jim Deville

Như @Simon đã nói, các cấu trúc cung cấp ngữ nghĩa "kiểu giá trị" vì vậy nếu bạn cần hành vi tương tự với kiểu dữ liệu tích hợp, hãy sử dụng một cấu trúc. Vì các cấu trúc được truyền qua bản sao, bạn muốn đảm bảo rằng chúng có kích thước nhỏ, khoảng 16 byte.

2
Scott Dorman

Các cấu trúc nằm trên Stack không phải là Heap, do đó chúng là luồng an toàn và nên được sử dụng khi triển khai mẫu đối tượng chuyển, bạn không bao giờ muốn sử dụng các đối tượng trên Heap mà chúng không ổn định, trong trường hợp này bạn muốn sử dụng Stack Call, đây là một trường hợp cơ bản để sử dụng một cấu trúc Tôi ngạc nhiên bởi tất cả các cách trả lời ở đây,

1
Jack

Hừm ...

Tôi sẽ không sử dụng bộ sưu tập rác làm đối số cho/chống lại việc sử dụng cấu trúc so với các lớp. Heap được quản lý hoạt động giống như một stack - tạo một đối tượng chỉ cần đặt nó ở đầu heap, gần như nhanh như phân bổ trên stack. Ngoài ra, nếu một đối tượng tồn tại trong thời gian ngắn và không tồn tại trong chu trình GC, thì việc phân bổ là miễn phí vì GC chỉ hoạt động với bộ nhớ vẫn có thể truy cập được. (Tìm kiếm MSDN, có một loạt bài viết về quản lý bộ nhớ .NET, tôi quá lười để tìm kiếm chúng).

Hầu hết thời gian tôi sử dụng một cấu trúc, tôi tự đá mình vì đã làm như vậy, bởi vì sau đó tôi phát hiện ra rằng việc có ngữ nghĩa tham chiếu sẽ khiến mọi thứ đơn giản hơn một chút.

Dù sao, bốn điểm trong bài viết MSDN được đăng ở trên có vẻ là một hướng dẫn tốt.

1
KG