.NET'te struct ve class arasındaki fark nedir?


Yanıtlar:


1058

.NET'te, iki tür kategori vardır: başvuru türleri ve değer türleri .

Yapılar değer tipleridir ve sınıflar referans tipleridir .

Genel fark, bir referans türünün yığın üzerinde yaşadığı ve bir değer türünün satır içinde yaşadığı, yani değişkeniniz veya alanınızın nerede olduğu tanımlanmışsa olmasıdır.

Değer türü içeren bir değişken , değer türü değerinin tamamını içerir . Bir yapı için bu, değişkenin tüm yapılarıyla birlikte tüm yapıyı içerdiği anlamına gelir.

Referans türü içeren bir değişken , bir işaretçi veya gerçek değerin bulunduğu bellekte başka bir yere başvuru içerir .

Bunun bir faydası vardır:

  • değer türleri her zaman bir değer içerir
  • referans türleri boş bir referans içerebilir , yani şu anda hiçbir şeye atıfta bulunmazlar

Dahili olarak, referans türleri s işaretçiler olarak uygulanır ve bunu bilerek ve değişken atamanın nasıl çalıştığını bilerek başka davranış kalıpları vardır:

  • bir değer türü değişkeninin içeriğini başka bir değişkene kopyalamak, tüm içeriği yeni değişkene kopyalar ve bu iki farklıyı ayırır. Başka bir deyişle, kopyadan sonra birindeki değişiklikler diğerini etkilemez
  • bir referans tipi değişkenin içeriğini başka bir değişkene kopyalamak, referansı kopyalar; bu, artık gerçek verilerin başka bir yerde aynı depolama alanına iki referansınız olduğu anlamına gelir . Başka bir deyişle, kopyadan sonra, bir referanstaki verileri değiştirmek diğerini de etkileyecektir, ancak yalnızca her iki yerde de aynı verilere baktığınız için

Değişkenleri veya alanları bildirirken, iki türün farkı şu şekildedir:

  • değişken: değer türü yığın üzerinde yaşar, referans türü yığın üzerinde gerçek belleğin yaşadığı bir yerde bir işaretçi olarak yaşar ( Eric Lipperts makale serisine dikkat edin : Yığın Bir Uygulama Ayrıntısıdır .)
  • sınıf / struct-field: değer türü , türün içinde yaşar, referans türü , türün içinde, gerçek belleğin yaşadığı yığın belleğinde bir işaretçi olarak yaşar.

43
Tam bir bütünlük adına, Eric Lippert'in yığının bir uygulama detayı olduğunu söylediğini söylemeliyim , yukarıdaki yığınlardan bahsettiğimde Eric'in gönderilerini aklınızda bulundurun.
Lasse V.Karlsen

2
Bunların hepsi C ++ için de geçerli mi?
Koray Tugay

9
bir diğer önemli fark kullanımdır. MSDN'den: "yapılar genellikle dikdörtgen koordinatları gibi ilgili değişkenlerin küçük bir grubunu kapsüllemek için kullanılır. Yapılar aynı zamanda yapıcılar, sabitler, alanlar, yöntemler, özellikler, dizinleyiciler, işleçler, olaylar ve iç içe türleri de içerebilir. üyeler gereklidir, bunun yerine türünüzü bir sınıf haline getirmeyi düşünmelisiniz. "
thewpfguy

4
@KorayTugay Hayır değil.
2013'te

9
C ++ yapı ve sınıftaki @KorayTugay, tek bir şey dışında kesinlikle eşdeğerdir - varsayılan erişim kısıtlaması (sınıf varsayılan olarak
özeldir

207

Her birinin kısa bir özeti:

Yalnızca Sınıflar:

  • Mirası destekleyebilir
  • Referans (işaretçi) türleri
  • Referans boş olabilir
  • Yeni örnek başına bellek ek yükü var

Yalnızca Yapılar:

  • Kalıtım desteklenemiyor
  • Değer türleri
  • Değere göre geçirilir (tamsayılar gibi)
  • Boş referans olamaz (Boş Null kullanılmadıkça)
  • Yeni kutu başına bellek yükü yok - 'kutulu' değilse

Hem Sınıflar hem de Yapılar:

  • Bileşik veri türleri genellikle mantıksal bir ilişkisi olan birkaç değişken içermek için mi kullanılır?
  • Yöntem ve olay içerebilir
  • Arayüzleri destekleyebilir

16
Bu cevabın pek doğru olmayan bazı kısımları var. Sınıflar her zaman öbek üzerinde olmaz ve yapılar her zaman yığının üzerinde olmaz. Mevcut istisnalar, bir sınıftaki yapı alanlarını, anonim yöntemlerde yakalanan değişkenleri ve lambda ifadelerini, yineleyici bloklarını ve daha önce bahsedilen kutulu değerleri içerir. Ancak yığın ve yığın tahsisi bir uygulama detayıdır ve değişebilir. Eric Lippart bunu burada tartışıyor . İndirdim, ancak güncellerseniz mutlu bir şekilde kaldıracağım.
Simon P Stevens

1
struct, diğer kaynaklardan / sınıflardan miras alınmasını desteklemez, ancak bir yapıya bir arabirim uygulayabilirsiniz.
thewpfguy

2
"Yeni örnek başına bellek ek yükü yok" yapılarını iddia ettiğinizde ne demek istediğinizi açıklığa kavuşturmak isteyebilirsiniz . İlk yorumum, yapıların sıfır bellek kullandığını - açıkça saçma olduğunu - iddia ediyor olmanızdı. Sonra belki de bir yapının, bir sınıftan farklı olarak, üye alanlarının toplamı kadar bellek gerektirdiğini söylemeye çalıştığını düşündüm. Ama sonra Googled c# struct memory overheadve Hans Passant'ın hayır olduğunu söyleyen bu cevabı buldum , durum da böyle değil. Yani ne yapmak yani?
Mark Amery

4
@MarkAmery "Hayır bellek yükü" ifadesi Sizin kimliğiyle aynı başlangıç reaksiyonu vardı, ama OP örneklerinin gerçeğine atıfta olduğunu düşünüyorum classörnekleri ise, hafızayı (çöp toplayıcı tarafından işlenen) yönetilen structdeğil .
Hutch

1
"Yapı değere göre geçirilir (tamsayılar gibi)" false: tüm değişken değere, yani başvuru türüne göre iletilir. Bir değişkeni referans olarak iletmek istiyorsanız "ref" anahtar sözcüğünü kullanmanız gerekir. jonskeet.uk/csharp/parameters.html#ref
Marco Staffoli

41

.NET'te, yapı ve sınıf bildirimleri referans türleri ve değer türleri arasında ayrım yapar.

Bir referans türünün etrafından geçtiğinizde, sadece bir tane saklanır. Örneğe erişen tüm kodlar aynı örneğe erişiyor.

Bir değer türünden geçtiğinizde, her biri bir kopyadır. Tüm kod kendi kopyası üzerinde çalışıyor.

Bu bir örnekle gösterilebilir:

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.

Bir sınıf için bu farklı olurdu

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.

Sınıflar hiçbir şey olamaz - başvuru null değerine işaret edebilir.

Yapılar asıl değerdir - boş olabilirler ama asla boş olamazlar. Bu nedenle yapıların her zaman parametresiz varsayılan bir kurucuları vardır - 'başlangıç ​​değerine' ihtiyaçları vardır.


@ T.Todua evet, yukarıda daha iyi cevaplar var, oy verdikten sonra cevap olarak seçtim ve aldım - bu, kuralları hala çözdüğümüz zaman SO'nun ilk beta'sından.
Keith

1
Beni doğru anladıysan bilmiyorum, cevabını gerçekten onayladım / kabul ettim (yukarıdaki cevapların aksine), çünkü seninkinin iyi örnekleri vardı (sadece teorik açıklamalar değil, yukarıdaki cevabın aksine, sadece örnekler olmadan teorik açıklamalar vardı) ).
T.Todua

24

Yapılar ve Sınıflar Arasındaki Fark:

  • Yapılar değer tipidir , Sınıflar ise referans tipidir .
  • Yapılar yığın depolanan ise sınıfları yığın depolanır .
  • Değer türleri, bildirildikleri yerde değerlerini tutar, ancak başvuru türü bir nesne belleğine başvuru içerir.
  • Kapsam türü kaybolduktan hemen sonra yok edilen değer türleri , referans türü ise kapsam kaybolduktan sonra sadece yok edilir. Nesne daha sonra çöp toplayıcı tarafından yok edilir.
  • Yapıyı başka bir yapıya kopyaladığınızda, bir yapının değiştirilmiş olarak o yapının yeni bir kopyası oluşturulur, diğer yapının değerini etkilemez.
  • Bir sınıfı başka bir sınıfa kopyaladığınızda, yalnızca başvuru değişkenini kopyalar.
  • Her iki başvuru değişkeni de öbek üzerinde aynı nesneyi gösterir. Bir değişkende yapılan değişiklik diğer referans değişkeni etkiler.
  • Yapıların yıkıcıları olamaz , ancak sınıfların yıkıcıları olabilir.
  • Yapıların açık parametresiz yapıcıları olamazken, sınıflar yapıları kalıtımı desteklemez, ancak sınıflar destekler. Her ikisi de bir arabirimden miras alınmasını destekler.
  • Yapılar kapalı tiptedir .

21

Microsoft'un Sınıf ve Yapı Arasındaki Seçimden ...

Genel bir kural olarak, bir çerçevedeki türlerin çoğunluğu sınıflar olmalıdır. Bununla birlikte, bir değer türünün özelliklerinin yapıların kullanımını daha uygun hale getirdiği bazı durumlar vardır.

Sınıf yerine bir yapıyı göz önünde bulundurun:

  • Tür örnekleri küçük ve genellikle kısa ömürlü ise veya genellikle diğer nesnelere gömülmüşse.

X Tür aşağıdaki özelliklerin tümüne sahip değilse bir yapıdan KAÇININ :

  • Mantıksal olarak ilkel tiplere (int, double, vb.) Benzer tek bir değeri temsil eder.
  • 16 baytın altında bir örnek boyutuna sahiptir.
  • Değişmez. (değiştirilemez)
  • Sık sık kutsanması gerekmeyecek.

19

Diğer cevaplarda açıklanan tüm farklılıklara ek olarak:

  1. Yapılar açık bir parametresiz yapıcı olamaz sınıf can oysa
  2. Yapılar yıkıcılara sahip olamazken , bir sınıf
  3. Yapılar başka bir yapıdan veya sınıftan miras alamazken , sınıf başka bir sınıftan miras alabilir. (Hem yapılar hem de sınıflar bir arabirimden uygulanabilir.)

Tüm farkları açıklayan bir videonun peşindeyseniz, Bölüm 29 - C # Eğitimi - C # 'daki sınıflar ve yapılar arasındaki farkları kontrol edebilirsiniz .


4
.Net dillerinin genellikle bir yapının parametresiz bir kurucu tanımlamasına izin vermemesi (dil derleyicisi tarafından yapılmasına izin verip vermeme kararı) yapıların var olabileceği ve ortaya çıkabileceği gerçeğinden çok daha önemlidir. herhangi bir kurucu çalıştırılmadan dış dünyaya (parametresiz bir kurucu tanımlanmış olsa bile). .Net dillerinin genellikle yapılar için parametresiz kurucuları yasaklamasının nedeni, bu tür kurucuların bazen çalıştırılması ve bazen çalıştırılmamasından kaynaklanan karışıklığı önlemektir.
Supercat

15

Sınıf örnekleri yönetilen yığında saklanır. Bir örneği 'içeren' tüm değişkenler, öbek üzerindeki örneğe basitçe bir referanstır. Bir nesnenin bir yönteme iletilmesi, nesnenin kendisi değil referansın bir kopyasının iletilmesine neden olur.

Yapılar (teknik olarak, değer türleri) ilkel tipte olduğu gibi kullanıldığı her yerde depolanır. İçerik, çalışma zamanı tarafından herhangi bir zamanda ve özelleştirilmiş bir kopya oluşturucu çağrılmadan kopyalanabilir. Bir yönteme bir değer türünün iletilmesi, yine herhangi bir özelleştirilebilir kod çağrılmadan tüm değerin kopyalanmasını içerir.

Ayrım, C ++ / CLI adları ile daha iyi yapılır: "ref sınıfı" ilk olarak açıklandığı gibi bir sınıftır, "value sınıfı" ikinci olarak açıklandığı gibi bir sınıftır. C # tarafından kullanılan "class" ve "struct" anahtar sözcükleri basitçe öğrenilmesi gereken bir şeydir.


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
Bu aslında oldukça görkemli: özetlenmiş ve bilgilendirici. Lütfen cevabınızı en az bir kez kanıtlamayı okumayı unutmayın - bazı satırlarda yapı ve sınıf açıklamalarını değiştirdiniz, ayrıca bazı yazım hataları var.
Robert Synoradzki

1
@ensisNoctis Bu hatalar için özür dileriz ve düzenleme için teşekkürler. Cevaplarımı tekrar okumalıyım 0x
0xaryan

8

Yapı ve Sınıf

Bir yapı bir değer türüdür, bu nedenle yığın üzerinde depolanır, ancak bir sınıf bir referans türüdür ve yığın üzerinde saklanır.

Bir yapı miras ve polimorfizmi desteklemez, ancak bir sınıf her ikisini de destekler.

Varsayılan olarak, tüm yapı üyeleri herkese açıktır, ancak sınıf üyeleri varsayılan olarak özel niteliktedir.

Yapı bir değer türü olduğundan, bir struct nesnesine null atayamayız, ancak bir sınıf için durum böyle değildir.


5
"Tüm yapı üyeleri herkese açık" ile ilgili: Yanılmıyorsam, bu yanlıştır. "İç içe sınıflar ve yapılar dahil olmak üzere sınıf üyeleri ve yapı üyeleri için erişim düzeyi varsayılan olarak gizlidir." msdn.microsoft.com/en-us/library/ms173121.aspx
Nate Cook

8

Diğer cevaplara eklemek için kayda değer bir temel fark vardır ve bu da verilerin diziler içinde depolanmasıdır, çünkü bu performans üzerinde önemli bir etkisi olabilir.

  • Bir yapı ile dizi, yapı örneğini içerir
  • Bir sınıfla, dizi bellekte başka bir yerde sınıfın bir örneğine bir işaretçi içerir

Hafızada bir dizi yapı şöyle görünür

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

Halbuki bir dizi sınıf şöyle görünür

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

Bir sınıf dizisiyle, ilgilendiğiniz değerler dizi içinde değil, bellekte başka bir yerde saklanır.

Uygulamaların büyük çoğunluğu için bu fark gerçekten önemli değildir, ancak yüksek performans kodunda bu, bellek içindeki verilerin yerini etkileyecek ve CPU önbelleğinin performansı üzerinde büyük bir etkiye sahip olacaktır. Yapıları kullanabildiğiniz / kullanmanız gerektiğinde sınıfları kullanmak, CPU'daki önbellek hatalarının sayısını büyük ölçüde artıracaktır.

Modern CPU'nun yaptığı en yavaş şey sayıları ezmemek, bellekten veri almak ve L1 önbellek isabetini RAM'den veri okumaktan çok daha hızlıdır.

İşte test edebileceğiniz bazı kodlar. Makinemde, sınıf dizisi üzerinden yineleme, struct dizisinden ~ 3x daha uzun sürer.

    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; "Yapılar değer tipleridir, bu nedenle bir değer depolarlar, sınıflar başvuru türleridir, bu nedenle bir sınıfa başvururlar." buradaki diğer cevaplardan daha önce anlamayan herkese anlamsız ve olası değildir ve "Sınıfla birlikte sınıf, farklı bir bellek alanındaki yeni sınıfa bir işaretçi içerir." sınıfları sınıf örnekleri ile karıştırır.
Mark Amery

@MarkAmery Biraz açıklamaya çalıştım. Gerçekten anlatmaya çalıştığım nokta, dizilerin değer ve referans türleriyle çalışma biçimleri arasındaki fark ve bunun performans üzerindeki etkisi oldu. Bu, diğer cevapların çoğunda yapıldığı için değer ve referans türlerinin ne olduğunu yeniden açıklamaya çalışmıyordum.
Will Calderwood

7

Sadece tamamlamak için, Equalsyöntemi kullanırken, tüm sınıflar ve yapılar tarafından miras alınan başka bir fark vardır .

Diyelim ki bir sınıfımız ve bir yapımız var:

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

ve Ana yöntemde 4 nesnemiz var.

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

Sonra:

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

Böylece yapılar, noktalar gibi sayısal benzeri nesneler için uygundur (x ve y koordinatlarını kaydedin). Ve sınıflar başkaları için uygundur. 2 kişinin adı, boyu, kilosu aynı olsa bile, hala 2 kişidir.


6

Yeni başlayanlar için, bir yapı referanstan ziyade değere göre geçirilir. Yapılar göreceli olarak basit veri yapıları için iyidir, sınıflar ise polimorfizm ve kalıtım yoluyla mimari açıdan çok daha fazla esnekliğe sahiptir.

Diğerleri muhtemelen size benden daha fazla ayrıntı verebilir, ancak benim için çalıştığım yapı basit olduğunda yapılar kullanıyorum.


4

Erişim belirticisinin temel farkının yanı sıra, yukarıda bahsedilen az sayıda şeyin yanı sıra, referans ve değer hakkında daha net bir fikir verecek olan çıktıya sahip bir kod örneği ile yukarıda belirtilenlerin bir kısmı da dahil olmak üzere bazı önemli farkları eklemek istiyorum.

yapılar:

  • Değer türleridir ve yığın tahsisi gerektirmez.
  • Bellek ayırma farklıdır ve yığın halinde saklanır
  • Küçük veri yapıları için kullanışlıdır
  • Performansı etkileyin, değeri yönteme aktardığımızda, tüm veri yapısını geçiririz ve hepsi yığına aktarılır.
  • Yapıcı yalnızca yapı değerinin kendisini döndürür (genellikle yığın üzerinde geçici bir konumda) ve bu değer daha sonra gerektiği gibi kopyalanır
  • Değişkenlerin her birinin kendi veri kopyası vardır ve birindeki işlemlerin diğerini etkilemesi mümkün değildir.
  • Kullanıcı tanımlı mirası desteklemez ve dolaylı olarak yazım nesnesinden miras alırlar

Sınıf:

  • Referans Türü değeri
  • Öbekte saklanır
  • Dinamik olarak ayrılmış bir nesneye başvuru depolamak
  • Yapıcılar yeni operatörle çağrılır, ancak bu yığın üzerinde bellek ayırmaz
  • Birden çok değişken aynı nesneye referans verebilir
  • Bir değişken üzerindeki işlemlerin, diğer değişken tarafından başvurulan nesneyi etkilemesi mümkündür

Kod Örneği

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

Çıktı

Struct Object'in başlangıç ​​değeri: 10

Inside Struct Metodu Struct Object'in Inside Method değeri: 20

Method Object nesnesinin Yöntem çağrısı değeri: 10

Sınıf Nesnesinin başlangıç ​​değeri: 10

Inside Class Metodu Class Object'in Inside Method değeri: 20

Class Object öğesinden Method call değeri: 20

Burada değere göre çağrı ile referansa göre çağrı arasındaki farkı açıkça görebilirsiniz.


4
  1. Bir sınıfta bildirilen olayların, iş parçacığı güvenliğini sağlamak için + = ve - = erişimi bir kilitle otomatik olarak kilitlenir (bu, sınıfın türünde statik olaylar kilitlenir). Bir yapıda bildirilen olayların + = ve - = erişimi otomatik olarak kilitlenmez. Yalnızca başvuru türü ifadesine kilitleyebileceğinizden, bir yapıya ilişkin bir kilit (bu) çalışmaz.

  2. Bir yapı örneği oluşturmak, çöp toplama (doğrudan veya dolaylı olarak bir başvuru türü örneği oluşturmadığı sürece) bir çöp toplama neden olamaz, ancak bir başvuru türü örneği oluşturmak çöp toplama neden olabilir.

  3. Bir yapı her zaman yerleşik bir genel varsayılan yapıcıya sahiptir.

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

    Bu, bir yapının her zaman somutlaştırılabileceği anlamına gelirken, tüm kurucuları özel olabileceğinden bir sınıf olmayabilir.

    class NonInstantiable
    {
        private NonInstantiable() // OK
        {
        }
    }
    
    struct Direct
    {
        private Direct() // Compile-time error
        {
        }
    }
  4. Bir yapının yıkıcısı olamaz. Yıkıcı sadece nesnenin geçersiz kılınmasıdır, kılık değiştirerek sonlandırın ve yapılar, değer türleri olan çöp toplama işlemine tabi değildir.

    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. Bir yapı dolaylı olarak mühürlenir, bir sınıf değildir.
    Bir yapı soyut olamaz, bir sınıf olabilir.
    Bir yapı yapıcısında: base () öğesini çağıramazken açık taban sınıfı olmayan bir sınıf çağırabilir.
    Bir yapı başka bir sınıfı genişletemez, bir sınıf da genişletebilir.
    Bir yapı, bir sınıfın yapabileceği korumalı üyeleri (alanlar, yuvalanmış türler) bildiremez.
    Bir yapı soyut işlev üyelerini bildiremez, soyut bir sınıf yapabilir.
    Bir yapı sanal işlev üyelerini bildiremez, bir sınıf bunu yapabilir.
    Bir yapı mühürlü fonksiyon üyelerini ilan edemez, bir sınıf yapabilir.
    Bir yapı geçersiz kılma işlev üyelerini bildiremez, bir sınıf bunu yapabilir.
    Bu kuralın tek istisnası, bir yapının System.Object, viz, Equals () ve GetHashCode () ve ToString () sanal yöntemlerini geçersiz kılabilmesidir.


Hangi durumlarda bir yapıya sahip bir olay kullanılır? Çok dikkatli yazılmış bir programın, bir yapıya sahip olayları işe yarayacak bir şekilde kullanabileceğini hayal edebilirim, ancak yalnızca yapı hiçbir zaman değerle kopyalanmadıysa veya aktarılmadıysa, bu durumda bir sınıf da olabilir.
supercat

@supercat Evet, bir yapıdaki statik olmayan bir olay çok garip olurdu ve yalnızca değişebilir yapılar için yararlı olacaktır ve olayın kendisi ("alan benzeri" bir olaysa), yapıyı "mutable" "kategorisinde ve ayrıca yapıda bir referans alanı alanı sunar. Yapılardaki statik olmayan olaylar kötü olmalıdır.
Jeppe Stig Nielsen

@JeppeStigNielsen: Bir yapının bir olaya sahip olmasının nerede mantıklı olduğunu görebildiğim tek desen, yapının amacı, proxy olarak davrandığı bir sınıf nesnesine değişmez bir referans tutmak olsaydı olurdu. Ancak, otomatik olaylar böyle bir senaryoda tamamen işe yaramaz olacaktır; bunun yerine, abone olma ve abonelikten çıkma olaylarının yapının arkasındaki sınıfa aktarılması gerekir. Keşke .NET, yapının Objectkutulu bir kopyasına bir referans tutabilecek, başlangıçta null gizli bir tür olan bir 'önbellek kutusu' yapı tipine sahip olsaydı (veya tanımlamayı mümkün kılar) .
supercat

1
@JeppeStigNielsen: Yapılar birçok proxy kullanım senaryosundaki sınıflardan daha iyi performans gösterir; yapıları kullanmayla ilgili en büyük sorun, boksun gerekli olduğu durumlarda, genellikle bir iç döngüye ertelenmesidir. Yapıların tekrar tekrar kutulara alınmasını önlemenin bir yolu olsaydı, daha birçok kullanım senaryosunda sınıflardan daha iyi olurdu.
supercat

4

Daha önce belirtildiği gibi: Sınıflar başvuru tipidir, Yapılar ise tüm sonuçları olan değer tipleridir.

Kuralın bir parçası olarak Çerçeve Tasarımı Yönergeleri, aşağıdaki durumlarda sınıflar yerine Yapılar kullanılmasını önerir:

  • 16 baytın altında bir örnek boyutuna sahiptir
  • Mantıksal olarak, ilkel türlere (int, double vb.) Benzer tek bir değeri temsil eder.
  • Değişmez
  • Sık sık kutsanması gerekmeyecek

3

Yöntemden birkaç sonuç döndürmeniz gerektiğinde ilginç bir "class vs struct" bulmaca durumu vardır: hangisini kullanacağınızı seçin. ValueTuple hikayesini biliyorsanız - Tuple (sınıf) 'dan daha etkili olması gerektiği için ValueTuple (struct) eklendiğini bilirsiniz. Ama rakamlarla ne anlama geliyor? İki test: biri 2 alana sahip yapı / sınıf, diğeri 8 alana sahip yapı / sınıf (4 sınıftan büyük boyutta işlemci keneleri açısından yapıdan daha etkili olmalı, ancak elbette GC yükü de dikkate alınmalıdır. ).

PS Özel durum 'sturct veya koleksiyonlu sınıf' için başka bir kriter var: 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 |

Kod testi:

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

Yapılar gerçek değerdir - boş olabilirler ama asla boş olamazlar

Bu doğrudur, ancak .NET 2 yapılarının Nullable sürümünü desteklediğini ve C # 'ın kullanımı kolaylaştırmak için bazı sözdizimsel şeker sağladığını da unutmayın.

int? value = null;
value  = 1;

1
Bunun sadece 'Sıfırlanabilir <int> değer = boş' yazan sözdizimsel şeker olduğunu unutmayın.
Erik van Brakel

@ErikvanBrakel Bu sadece sözdizimsel şeker değil. Farklı boks kuralları (object)(default(int?)) == null, başka bir değer türüyle yapamayacağınız anlamına gelir , çünkü burada sadece şekerden daha fazlası vardır. Tek şeker int?içindir Nullable<int>.
Jon Hanna

1; bu, yapılar ve sınıflar arasındaki farkın ne olduğu sorusunu ele almaz ve bu nedenle, yanıtladığınız yanıta ayrı bir yanıt değil, bir yorum olmalıdır. (Belki de site normları Ağustos 2008'de farklı olmasına rağmen!)
Mark Amery

1

İlkel bir değer türünün veya yapı türünün her değişkeni veya alanı, tüm alanları (genel ve özel) dahil olmak üzere bu türün benzersiz bir örneğini tutar. Buna karşılık, referans türlerinin değişkenleri veya alanları null tutabilir veya başka yerlerde depolanan ve herhangi bir sayıda başka referansın da mevcut olabileceği bir nesneye işaret edebilir. Bir yapının alanları, yığın üzerinde veya başka bir yığın nesnesinin parçası olabilen, o yapı türünün değişkeni veya alanı ile aynı yerde depolanır .

Bir ilkel değer türünün değişkenini veya alanını oluşturmak, onu varsayılan bir değerle oluşturur; yapı türünde bir değişken veya alan oluşturmak yeni bir örnek oluşturur ve buradaki tüm alanları varsayılan biçimde oluşturur. Bir başvuru türünün yeni bir örneğinin oluşturulması, içindeki tüm alanları varsayılan biçimde oluşturarak ve türe bağlı olarak isteğe bağlı ek kod çalıştırarak başlayacaktır.

İlkel türdeki bir değişkeni veya alanı diğerine kopyalamak değeri kopyalar. Bir değişken veya yapı türü alanını diğerine kopyalamak, önceki örneğin tüm alanlarını (genel ve özel) sonraki örneğe kopyalar. Bir değişken veya referans türü alanını diğerine kopyalamak, ikincisinin diğerine (varsa) aynı örneğe başvurmasına neden olur.

C ++ gibi bazı dillerde, bir türün semantik davranışının saklanma biçiminden bağımsız olduğunu, ancak bunun .NET için geçerli olmadığını unutmayın. Bir tür değişebilir değer semantiği uygularsa, bu türdeki bir değişkeni başka bir değişkene kopyalamak, ilkin özelliklerini ikinci tarafından başvurulan başka bir örneğe kopyalar ve onu değiştirmek için ikinci bir üyeyi kullanmak, bu ikinci örneğin değiştirilmesine neden olur. , ama ilk değil. Bir tür değişebilir referans anlambilimi uygularsa, bir değişkeni diğerine kopyalamak ve nesneyi değiştirmek için ikincisinin bir üyesini kullanmak ilk değişken tarafından başvurulan nesneyi etkiler; değişmez semantiğe sahip türler mutasyona izin vermez, bu nedenle kopyalamanın yeni bir örnek oluşturup oluşturmadığını veya birincisine başka bir başvuru oluşturup oluşturmayacağını anlamsal olarak fark etmez.

.NET'te, değer alanlarının, tüm alanlarının da benzer şekilde yapabilmesi şartıyla, yukarıdaki semantiklerden herhangi birini uygulaması mümkündür. Bununla birlikte, bir referans tipi sadece değişebilir referans anlambilimi veya değişmez anlambilimi uygulayabilir; Değişebilir referans türlerinin bulunduğu değer türleri, değişebilir referans semantiği veya garip karma semantik uygulamakla sınırlıdır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.