Apa perbedaan antara struct dan kelas di .NET?

Jawaban:

1058

Di .NET, ada dua kategori tipe, tipe referensi dan tipe nilai .

Structs adalah tipe nilai dan kelas adalah tipe referensi .

Perbedaan umum adalah bahwa tipe referensi tinggal di heap, dan tipe nilai tinggal inline, yaitu, di mana pun itu variabel Anda atau bidang didefinisikan.

Variabel yang berisi tipe nilai berisi seluruh nilai tipe nilai. Untuk struct, itu berarti bahwa variabel berisi seluruh struct, dengan semua bidangnya.

Variabel yang berisi tipe referensi berisi pointer, atau referensi ke tempat lain di memori di mana nilai aktual berada.

Ini memiliki satu manfaat, untuk mulai dengan:

  • tipe nilai selalu mengandung nilai
  • tipe referensi dapat berisi referensi - nol , yang berarti bahwa mereka tidak merujuk pada apa pun pada saat ini

Secara internal, tipe referensi diimplementasikan sebagai pointer, dan mengetahui itu, dan mengetahui bagaimana tugas variabel bekerja, ada pola perilaku lainnya:

  • menyalin isi variabel tipe nilai ke variabel lain, menyalin seluruh konten ke variabel baru, membuat keduanya berbeda. Dengan kata lain, setelah salinan, perubahan yang satu tidak akan memengaruhi yang lain
  • menyalin isi variabel tipe referensi ke variabel lain, menyalin referensi, yang berarti Anda sekarang memiliki dua referensi yang sama di tempat lain penyimpanan data aktual. Dengan kata lain, setelah salinan, mengubah data dalam satu referensi juga akan memengaruhi yang lain, tetapi hanya karena Anda benar-benar hanya melihat data yang sama di kedua tempat

Saat Anda mendeklarasikan variabel atau bidang, inilah perbedaan kedua jenis:

  • variabel: tipe nilai tinggal di stack, tipe referensi tinggal di stack sebagai penunjuk ke suatu tempat di memori tumpukan di mana memori yang sebenarnya tinggal (meskipun perhatikan seri artikel Eric Lipperts: The Stack Is an Detail Implementasi .)
  • class / struct-field: tipe nilai hidup sepenuhnya di dalam tipe, tipe referensi tinggal di dalam tipe sebagai pointer ke suatu tempat di memori tumpukan di mana memori yang sebenarnya tinggal.
Lasse V. Karlsen
sumber
43
Demi kelengkapan penuh, saya harus menyebutkan bahwa Eric Lippert telah mengatakan bahwa stack adalah detail implementasi , setiap kali saya menyebutkan stack di atas, ingatlah posting Eric.
Lasse V. Karlsen
2
Apakah ini juga berlaku untuk C ++?
Koray Tugay
9
Perbedaan penting lainnya adalah penggunaan. Dari MSDN: "struct biasanya digunakan untuk merangkum sekelompok kecil variabel terkait, seperti koordinat persegi panjang. Structs juga dapat berisi konstruktor, konstanta, bidang, metode, properti, pengindeks, operator, peristiwa, dan tipe bersarang, meskipun jika beberapa seperti diperlukan anggota, Anda sebaiknya mempertimbangkan membuat tipe Anda sebagai kelas. "
thewpfguy
4
@ KorayTugay Tidak, tidak.
ZoomIn
9
@ KorayTugay dalam C ++ struct dan kelas benar-benar setara kecuali untuk satu hal - pembatasan akses default (kelas memiliki pribadi secara default, struct memiliki publik)
berkus
207

Ringkasan masing-masing:

Kelas Saja:

  • Dapat mendukung warisan
  • Apakah tipe referensi (penunjuk)
  • Referensi dapat berupa nol
  • Memiliki overhead memori per instance baru

Hanya Structs:

  • Tidak dapat mendukung warisan
  • Apakah tipe nilai
  • Disahkan oleh nilai (seperti bilangan bulat)
  • Tidak dapat memiliki referensi nol (kecuali Nullable digunakan)
  • Tidak memiliki overhead memori per instance baru - kecuali 'kotak'

Kelas dan Struktur:

  • Apakah tipe data majemuk biasanya digunakan untuk memuat beberapa variabel yang memiliki beberapa hubungan logis
  • Dapat berisi metode dan acara
  • Dapat mendukung antarmuka
Thomas Bratt
sumber
16
Ada beberapa bagian dari jawaban ini yang kurang tepat. Kelas tidak selalu naik heap, dan struct tidak selalu naik stack. Pengecualian saat ini mencakup bidang struct pada kelas, variabel yang ditangkap dalam metode anonim dan ekspresi lambda, blok iterator, dan nilai-nilai kotak yang telah disebutkan. Tapi alokasi stack vs heap adalah detail implementasi dan dapat berubah. Eric lippart membahas ini di sini . Saya telah turun undian, tetapi dengan senang hati akan menghapusnya jika Anda memperbarui.
Simon P Stevens
1
struct tidak mendukung warisan dari stucts / kelas lain, tetapi Anda BISA mengimplementasikan antarmuka pada struct.
thewpfguy
2
Anda mungkin ingin mengklarifikasi apa yang Anda maksud ketika Anda mengklaim bahwa struct "Tidak memiliki overhead memori per instance baru" . Interpretasi pertama saya adalah bahwa Anda mengklaim - jelas bukan kepalang - bahwa struct menggunakan nol memori. Lalu saya berpikir bahwa mungkin Anda mencoba untuk mengatakan bahwa struct, tidak seperti kelas, membutuhkan memori persis sebanyak jumlah bidang anggota, dan tidak lebih. Tetapi kemudian saya mencari Google c# struct memory overheaddan menemukan jawaban ini oleh Hans Passant yang mengatakan bahwa tidak, itu juga tidak benar. Jadi apa yang Anda maksud?
Mark Amery
4
@MarkAmery Saya memiliki reaksi awal yang sama seperti yang Anda id untuk ekspresi "tidak ada memori overhead", tapi saya pikir OP mengacu pada kenyataan bahwa contoh classdikelola memori (ditangani oleh pengumpul sampah), sedangkan contoh structtidak .
Hutch
1
"Struct dilewatkan oleh nilai (seperti bilangan bulat)" adalah salah: semua variabel dilewatkan oleh nilai, juga tipe referensi. Jika Anda ingin memberikan variabel dengan referensi, Anda harus menggunakan kata kunci "ref". jonskeet.uk/csharp/parameters.html#ref
Marco Staffoli
41

Dalam .NET deklarasi struct dan kelas membedakan antara tipe referensi dan tipe nilai.

Ketika Anda melewati jenis referensi hanya ada satu yang benar-benar disimpan. Semua kode yang mengakses instance sedang mengakses yang sama.

Ketika Anda melewati suatu tipe nilai masing-masing adalah salinan. Semua kode bekerja pada salinannya sendiri.

Ini dapat ditunjukkan dengan contoh:

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.

Untuk kelas ini akan berbeda

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.

Kelas bisa berupa apa saja - referensi dapat menunjuk ke nol.

Struct adalah nilai aktual - mereka bisa kosong tetapi tidak pernah nol. Karena alasan ini, struct selalu memiliki konstruktor default tanpa parameter - mereka memerlukan 'nilai awal'.

Keith
sumber
@ T.Todua ya, ada jawaban yang lebih baik di atas, bahwa saya memilih dan memilih sebagai jawaban setelah memberikan yang ini - ini dari beta awal SO ketika kami masih mencari tahu aturannya.
Keith
1
Saya tidak tahu apakah Anda memahami saya dengan benar, saya benar-benar menaikkan / menerima jawaban Anda (berlawanan dengan jawaban di atas), karena Anda memiliki contoh yang baik (tidak hanya penjelasan teoretis, sebagai lawan dari jawaban di atas, yang hanya memiliki penjelasan teoretis tanpa contoh ).
T.Todua
24

Perbedaan antara Structs dan Classes:

  • Struct adalah tipe nilai sedangkan Class adalah tipe referensi .
  • Struct disimpan di stack sedangkan Classes disimpan di heap .
  • Tipe nilai menyimpan nilainya dalam memori di mana mereka dinyatakan, tetapi tipe referensi memegang referensi ke memori objek.
  • Tipe nilai dihancurkan segera setelah ruang lingkup hilang sedangkan tipe referensi hanya variabel yang hancur setelah ruang lingkup hilang. Objek tersebut kemudian dihancurkan oleh pemulung.
  • Ketika Anda menyalin struct ke struct lain, salinan baru struct yang akan dibuat dimodifikasi dari satu struct tidak akan mempengaruhi nilai struct lainnya.
  • Ketika Anda menyalin kelas ke kelas lain, itu hanya menyalin variabel referensi.
  • Kedua variabel referensi menunjuk ke objek yang sama pada heap. Perubahan ke satu variabel akan mempengaruhi variabel referensi lainnya.
  • Struct tidak dapat memiliki destruktor , tetapi kelas dapat memiliki destructor.
  • Structs tidak dapat memiliki konstruktor tanpa parameter eksplisit sedangkan class can struct tidak mendukung pewarisan, tetapi kelas melakukannya. Keduanya mendukung warisan dari suatu antarmuka.
  • Struct adalah tipe yang disegel .
shana
sumber
21

Dari Microsoft Memilih Antara Kelas dan Struktur ...

Sebagai aturan praktis, sebagian besar jenis dalam suatu kerangka kerja harus kelas. Namun, ada beberapa situasi di mana karakteristik tipe nilai membuatnya lebih tepat untuk menggunakan struct.

PERTIMBANGKAN struct bukan kelas:

  • Jika instance dari tipe kecil dan umumnya berumur pendek atau umumnya tertanam di objek lain.

X HINDARI struct kecuali tipe tersebut memiliki semua karakteristik berikut:

  • Secara logis mewakili nilai tunggal, mirip dengan tipe primitif (int, dobel, dll.).
  • Ini memiliki ukuran instance di bawah 16 byte.
  • Itu tidak berubah. (tidak bisa diubah)
  • Itu tidak harus sering kotak.
Sunsetquest
sumber
19

Selain semua perbedaan yang dijelaskan dalam jawaban lain:

  1. Structs tidak dapat memiliki konstruktor tanpa parameter eksplisit sedangkan kelas bisa
  2. Struct tidak dapat memiliki destructor , sedangkan sebuah kelas bisa
  3. Struct tidak dapat mewarisi dari struct atau kelas lain sedangkan kelas dapat mewarisi dari kelas lain. (Baik struct dan kelas dapat diimplementasikan dari sebuah antarmuka.)

Jika Anda mencari video yang menjelaskan semua perbedaan, Anda dapat melihat Bagian 29 - C # Tutorial - Perbedaan antara kelas dan struct di C # .

Venkat
sumber
4
Jauh lebih signifikan daripada fakta bahwa bahasa .net umumnya tidak akan memungkinkan struct untuk mendefinisikan konstruktor tanpa parameter (keputusan apakah atau tidak membiarkannya dibuat oleh kompiler bahasa) adalah fakta bahwa struktur dapat muncul dan diekspos ke dunia luar tanpa konstruktor apa pun yang telah dijalankan (bahkan ketika konstruktor tanpa parameter didefinisikan). Alasan. Bersih bahasa umumnya melarang konstruktor parameterless untuk struct adalah untuk menghindari kebingungan yang akan dihasilkan dari memiliki konstruktor seperti itu kadang-kadang dijalankan dan kadang-kadang tidak.
supercat
15

Contoh kelas disimpan di heap yang dikelola. Semua variabel 'mengandung' instance hanyalah referensi ke instance pada heap. Melewati objek ke metode menghasilkan salinan referensi yang diteruskan, bukan objek itu sendiri.

Struktur (secara teknis, tipe nilai) disimpan di mana pun mereka digunakan, seperti tipe primitif. Konten dapat disalin oleh runtime kapan saja dan tanpa meminta copy-constructor yang disesuaikan. Melewati tipe nilai ke metode melibatkan menyalin seluruh nilai, lagi-lagi tanpa meminta kode yang dapat disesuaikan.

Perbedaannya dibuat lebih baik dengan nama C ++ / CLI: "kelas ref" adalah kelas seperti yang dijelaskan pertama, "kelas nilai" adalah kelas seperti yang dijelaskan kedua. Kata kunci "kelas" dan "struct" seperti yang digunakan oleh C # hanyalah sesuatu yang harus dipelajari.

Zooba
sumber
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                                |
+------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------+
0xaryan
sumber
1
Ini sebenarnya sangat bagus: dirangkum dan informatif. Harap hanya ingat untuk membaca-baca jawaban Anda setidaknya sekali - Anda bertukar penjelasan struct dan kelas dalam beberapa baris, juga ada beberapa kesalahan ketik.
Robert Synoradzki
1
@ensisNoctis Maaf atas kesalahan itu dan terima kasih atas hasil editnya. Saya harus membaca kembali jawaban saya 😅
0xaryan
8

Struktur vs Kelas

Struktur adalah tipe nilai sehingga disimpan di tumpukan, tetapi kelas adalah tipe referensi dan disimpan di heap.

Struktur tidak mendukung warisan, dan polimorfisme, tetapi kelas mendukung keduanya.

Secara default, semua anggota struct adalah publik tetapi anggota kelas secara default bersifat pribadi.

Karena struktur adalah tipe nilai, kami tidak dapat menetapkan null ke objek struct, tetapi itu bukan kasus untuk kelas.

Swagatika dhal
sumber
5
Mengenai "semua anggota struct adalah publik": Jika saya tidak salah, itu tidak benar. "Level akses untuk anggota kelas dan anggota struct, termasuk kelas dan struct yang bersarang, adalah pribadi secara default." msdn.microsoft.com/en-us/library/ms173121.aspx
Nate Cook
8

Untuk menambah jawaban lain, ada satu perbedaan mendasar yang perlu diperhatikan, dan itu adalah bagaimana data disimpan dalam array karena ini dapat memiliki efek besar pada kinerja.

  • Dengan struct, array berisi instance dari struct
  • Dengan kelas, array berisi pointer ke instance kelas di tempat lain dalam memori

Jadi array struct terlihat seperti ini di memori

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

Sedangkan array kelas terlihat seperti ini

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

Dengan array kelas, nilai yang Anda minati tidak disimpan dalam array, tetapi di tempat lain di memori.

Untuk sebagian besar aplikasi perbedaan ini tidak terlalu penting, namun, dalam kode kinerja tinggi ini akan mempengaruhi lokalitas data dalam memori dan memiliki dampak besar pada kinerja cache CPU. Menggunakan kelas ketika Anda bisa / seharusnya menggunakan struct akan secara besar-besaran meningkatkan jumlah cache yang gagal pada CPU.

Hal paling lambat yang dilakukan CPU modern adalah bukan angka-angka, itu mengambil data dari memori, dan hit cache L1 berkali-kali lebih cepat daripada membaca data dari RAM.

Berikut beberapa kode yang dapat Anda uji. Di mesin saya, iterasi melalui array kelas membutuhkan ~ 3x lebih lama dari array 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}");
    }
Will Calderwood
sumber
-1; "Structs adalah tipe nilai, jadi mereka menyimpan nilai, kelas adalah tipe referensi, jadi mereka mereferensikan sebuah kelas." tidak jelas dan tidak masuk akal bagi siapa pun yang belum memahaminya dari jawaban lain di sini, dan "Dengan kelas yang berisi kelas hanya akan berisi pointer ke kelas baru di area memori yang berbeda." membingungkan kelas dengan instance kelas.
Mark Amery
@MarkAmery Saya sudah mencoba mengklarifikasi sedikit. Maksud saya benar-benar mencoba untuk membuat perbedaan cara array bekerja dengan nilai dan jenis referensi dan efeknya terhadap kinerja. Saya tidak mencoba menjelaskan kembali nilai dan tipe referensi apa karena ini dilakukan di banyak jawaban lain.
Will Calderwood
7

Hanya untuk membuatnya lengkap, ada perbedaan lain ketika menggunakan Equalsmetode ini, yang diwarisi oleh semua kelas dan struktur.

Katakanlah kita memiliki kelas dan struktur:

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

dan dalam metode Utama, kami memiliki 4 objek.

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;
}

Kemudian:

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

Jadi , struktur cocok untuk objek seperti numerik, seperti titik (simpan koordinat x dan y). Dan kelas cocok untuk orang lain. Bahkan jika 2 orang memiliki nama, tinggi, berat yang sama ..., mereka masih 2 orang.

Ning
sumber
6

Nah, untuk permulaan, sebuah struct dilewatkan oleh nilai daripada oleh referensi. Structs baik untuk struktur data yang relatif sederhana, sementara kelas memiliki lebih banyak fleksibilitas dari sudut pandang arsitektur melalui polimorfisme dan pewarisan.

Orang lain mungkin bisa memberi Anda lebih banyak detail daripada saya, tetapi saya menggunakan struct ketika struktur yang saya gunakan sederhana.

Ed S.
sumber
4

Selain perbedaan dasar dari specifier akses, dan beberapa yang disebutkan di atas saya ingin menambahkan beberapa perbedaan utama termasuk beberapa yang disebutkan di atas dengan sampel kode dengan output, yang akan memberikan ide yang lebih jelas tentang referensi dan nilai

Structs:

  • Apakah tipe nilai dan tidak memerlukan alokasi tumpukan.
  • Alokasi memori berbeda dan disimpan dalam tumpukan
  • Berguna untuk struktur data kecil
  • Mempengaruhi kinerja, saat kami meneruskan nilai ke metode, kami meneruskan seluruh struktur data dan semua diteruskan ke tumpukan.
  • Konstruktor hanya mengembalikan nilai struct itu sendiri (biasanya di lokasi sementara di stack), dan nilai ini kemudian disalin seperlunya
  • Masing-masing variabel memiliki salinan data mereka sendiri, dan tidak mungkin operasi pada satu untuk mempengaruhi yang lain.
  • Jangan mendukung warisan yang ditentukan pengguna, dan mereka secara implisit mewarisi dari objek tipe

Kelas:

  • Nilai Jenis Referensi
  • Disimpan dalam Heap
  • Menyimpan referensi ke objek yang dialokasikan secara dinamis
  • Konstruktor dipanggil dengan operator baru, tetapi itu tidak mengalokasikan memori pada heap
  • Beberapa variabel mungkin memiliki referensi ke objek yang sama
  • Dimungkinkan untuk operasi pada satu variabel untuk mempengaruhi objek yang dirujuk oleh variabel lain

Contoh kode

    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;
        }
    }

Keluaran

Nilai awal dari Struct Object adalah: 10

Metode Inside Struct Nilai Inside Method dari Struct Object adalah: 20

Setelah nilai panggilan Metode Objek Struct adalah: 10

Nilai awal Obyek Kelas adalah: 10

Metode Inside Class Nilai Inside Method dari Class Object adalah: 20

Nilai panggilan Metode Setelah Kelas Obyek adalah: 20

Di sini Anda dapat dengan jelas melihat perbedaan antara panggilan berdasarkan nilai dan panggilan dengan referensi.

Arijit Mukherjee
sumber
4
  1. Acara yang dideklarasikan di kelas memiliki akses + = dan - = mereka secara otomatis dikunci melalui kunci (ini) untuk membuatnya aman (acara statis dikunci pada tipe kelas). Acara yang dideklarasikan dalam struct tidak memiliki akses + = dan - = mereka yang dikunci secara otomatis. Kunci (ini) untuk struct tidak akan berfungsi karena Anda hanya bisa mengunci pada ekspresi tipe referensi.

  2. Membuat instance struct tidak dapat menyebabkan pengumpulan sampah (kecuali jika konstruktor secara langsung atau tidak langsung membuat instance tipe referensi) sedangkan membuat instance tipe referensi dapat menyebabkan pengumpulan sampah.

  3. Sebuah struct selalu memiliki konstruktor default publik bawaan.

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

    Ini berarti bahwa struct selalu instantiable sedangkan kelas mungkin tidak karena semua konstruktornya bisa pribadi.

    class NonInstantiable
    {
        private NonInstantiable() // OK
        {
        }
    }
    
    struct Direct
    {
        private Direct() // Compile-time error
        {
        }
    }
  4. Str tidak dapat memiliki destruktor. Sebuah destructor hanyalah sebuah override objek. Finalisasi dalam penyamaran, dan struct, sebagai tipe nilai, tidak dikenakan pengumpulan sampah.

    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. Struct secara implisit disegel, kelas tidak.
    Sebuah struct tidak bisa abstrak, sebuah kelas bisa.
    Sebuah struct tidak dapat memanggil: base () dalam konstruktornya sedangkan kelas tanpa kelas dasar eksplisit dapat.
    Sebuah struct tidak dapat memperluas kelas lain, sebuah kelas bisa.
    Sebuah struct tidak dapat mendeklarasikan anggota yang dilindungi (misalnya, bidang, tipe bersarang) sebuah kelas bisa.
    Sebuah struct tidak dapat mendeklarasikan anggota fungsi abstrak, sebuah kelas abstrak bisa.
    Sebuah struct tidak dapat mendeklarasikan anggota fungsi virtual, sebuah kelas dapat.
    Sebuah struct tidak dapat mendeklarasikan anggota fungsi yang disegel, sebuah kelas dapat.
    Sebuah struct tidak bisa mendeklarasikan override fungsi anggota, sebuah kelas bisa.
    Satu-satunya pengecualian untuk aturan ini adalah bahwa struct dapat menimpa metode virtual System.Object, viz, Equals (), dan GetHashCode (), dan ToString ().

Zain Ali
sumber
Dalam keadaan apa seseorang akan menggunakan acara dengan struct? Saya bisa membayangkan bahwa program yang ditulis dengan sangat hati-hati dapat menggunakan peristiwa dengan struct dengan cara yang akan bekerja, tetapi hanya jika struct tidak pernah disalin atau diteruskan oleh nilai, dalam hal ini mungkin juga kelas.
supercat
@supercat Ya, acara non-statis dalam sebuah struct akan sangat aneh, dan itu akan berguna hanya untuk struct yang bisa berubah, dan event itu sendiri (jika itu adalah "field-like" event) mengubah struct menjadi "bisa berubah + msgstr "kategori dan juga perkenalkan bidang tipe referensi dalam struct. Peristiwa non-statis di struct harus menjadi kejahatan.
Jeppe Stig Nielsen
@ JeppeStigNielsen: Satu-satunya pola yang bisa saya lihat di mana masuk akal bagi seorang struct untuk mengadakan suatu acara adalah jika tujuan dari struct adalah untuk menyimpan referensi yang tidak dapat diubah ke objek kelas yang mana ia berperilaku sebagai proxy. Meskipun begitu, peristiwa otomatis sama sekali tidak berguna dalam skenario seperti itu; sebaliknya, berlangganan dan berhenti berlangganan acara harus diteruskan ke kelas di belakang struktur. Saya berharap .NET memiliki (atau akan memungkinkan untuk mendefinisikan) tipe struktur 'cache-box "dengan bidang tipe tersembunyi awalnya-nol Object, yang akan menyimpan referensi ke salinan kotak dari struct.
supercat
1
@JeppeStigNielsen: Struct mengungguli kelas dalam banyak skenario penggunaan proxy; masalah terbesar dengan menggunakan struct adalah bahwa dalam kasus-kasus di mana tinju akhirnya diperlukan, sering akhirnya ditunda ke loop dalam. Jika ada cara untuk menghindari memiliki struktur kotak berulang kali , mereka akan lebih baik daripada kelas di banyak skenario penggunaan lainnya.
supercat
4

Seperti yang disebutkan sebelumnya: Kelas adalah tipe referensi sedangkan Structs adalah tipe nilai dengan semua konsekuensinya.

Sebagai pedoman praktis, Pedoman Desain Kerangka merekomendasikan penggunaan Struct daripada kelas jika:

  • Ini memiliki ukuran instance di bawah 16 byte
  • Secara logis mewakili nilai tunggal, mirip dengan tipe primitif (int, dobel, dll.)
  • Itu tidak berubah
  • Itu tidak harus sering kotak
kb9
sumber
3

Ada satu kasus menarik dari teka-teki "kelas vs struct" - situasi ketika Anda harus mengembalikan beberapa hasil dari metode ini: pilih yang akan digunakan. Jika Anda tahu cerita ValueTuple - Anda tahu bahwa ValueTuple (struct) ditambahkan karena itu harus lebih efektif daripada Tuple (kelas). Tapi apa artinya angka? Dua tes: satu adalah struct / kelas yang memiliki 2 bidang, lainnya dengan struct / kelas yang memiliki 8 bidang (dengan dimensi lebih dari 4 - kelas harus menjadi lebih efektif daripada struct dalam hal kutu prosesor, tetapi tentu saja beban GC juga harus dipertimbangkan ).

PS Tolok ukur lain untuk kasus khusus 'kokoh atau kelas dengan koleksi' ada di sana: 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 |

Tes kode:

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;
            }
        }
    }
}
Roman Pokrovskij
sumber
2

Struct adalah nilai aktual - mereka bisa kosong tetapi tidak pernah nol

Ini benar, namun juga perhatikan bahwa pada. NET 2 struct mendukung versi Nullable dan C # memasok beberapa gula sintaksis untuk membuatnya lebih mudah digunakan.

int? value = null;
value  = 1;
denis phillips
sumber
1
Ketahuilah bahwa ini hanya gula sintaksis yang bertuliskan 'Nullable <int> value = null;'
Erik van Brakel
@ErikvanBrakel Itu bukan hanya gula sintaksis. Aturan tinju yang berbeda berarti (object)(default(int?)) == nullyang tidak dapat Anda lakukan dengan jenis nilai lain, karena ada lebih dari sekadar gula yang terjadi di sini. Gula hanya int?untuk Nullable<int>.
Jon Hanna
-1; ini tidak menjawab pertanyaan tentang apa perbedaan antara struct dan kelas, dan dengan demikian, seharusnya komentar pada jawaban yang Anda balas, bukan jawaban yang terpisah. (Meskipun mungkin norma situs berbeda pada Agustus 2008!)
Mark Amery
1

Setiap variabel atau bidang tipe nilai primitif atau tipe struktur memiliki contoh unik dari tipe itu, termasuk semua bidangnya (publik dan pribadi). Sebaliknya, variabel atau bidang tipe referensi dapat dianggap nol, atau dapat merujuk ke suatu objek, disimpan di tempat lain, di mana sejumlah referensi lain mungkin juga ada. Bidang struct akan disimpan di tempat yang sama dengan variabel atau bidang tipe struktur itu, yang mungkin berada di tumpukan atau mungkin menjadi bagian dari objek tumpukan lain.

Membuat variabel atau bidang tipe nilai primitif akan membuatnya dengan nilai default; membuat variabel atau bidang tipe struktur akan membuat contoh baru, membuat semua bidang di dalamnya secara default. Membuat instance baru dari jenis referensi akan mulai dengan membuat semua bidang di dalamnya secara default, dan kemudian menjalankan kode tambahan opsional tergantung pada jenisnya.

Menyalin satu variabel atau bidang dari tipe primitif ke yang lain akan menyalin nilainya. Menyalin satu variabel atau bidang tipe struktur ke yang lain akan menyalin semua bidang (publik dan pribadi) dari contoh sebelumnya ke contoh terakhir. Menyalin satu variabel atau bidang tipe referensi ke yang lain akan menyebabkan yang terakhir merujuk ke instance yang sama dengan yang sebelumnya (jika ada).

Penting untuk dicatat bahwa dalam beberapa bahasa seperti C ++, perilaku semantik suatu tipe tidak tergantung pada bagaimana ia disimpan, tetapi itu tidak berlaku untuk .NET. Jika suatu tipe mengimplementasikan semantik nilai yang dapat diubah, menyalin satu variabel dari tipe itu ke yang lain menyalin properti dari instance pertama ke yang lain, dirujuk oleh yang kedua, dan menggunakan anggota dari yang kedua untuk bermutasi akan menyebabkan instance kedua tersebut diubah , tapi bukan yang pertama. Jika suatu tipe mengimplementasikan semantik referensi yang dapat berubah, menyalin satu variabel ke variabel lain dan menggunakan anggota yang kedua untuk bermutasi objek akan mempengaruhi objek yang dirujuk oleh variabel pertama; tipe dengan semantik yang tidak berubah tidak memungkinkan mutasi, jadi tidak masalah secara semantik apakah penyalinan membuat instance baru atau membuat referensi lain ke yang pertama.

Di .NET, dimungkinkan untuk tipe nilai untuk mengimplementasikan semantik di atas, asalkan semua bidang mereka dapat melakukan hal yang sama. Namun, tipe referensi hanya dapat menerapkan semantik referensi yang bisa berubah atau semantik yang tidak dapat diubah; tipe nilai dengan bidang tipe referensi yang bisa berubah terbatas baik untuk mengimplementasikan semantik referensi yang dapat diubah atau semantik hibrid yang aneh.

supercat
sumber