Ne zaman C # bir sınıf yerine bir yapı kullanmalıyım?


1390

Ne zaman C # sınıf değil, struct kullanmalısınız? Kavramsal modelim, öğelerin yalnızca değer türlerinin bir koleksiyonu olduğu zamanlarda yapıların kullanılmasıdır . Hepsini mantıklı bir şekilde birbirine bağlı bir bütün halinde tutmanın bir yolu.

Bu kurallara burada rastladım :

  • Bir yapı tek bir değeri temsil etmelidir.
  • Bir yapıda 16 bayttan daha az bellek alanı olmalıdır.
  • Bir yapı, oluşturulduktan sonra değiştirilmemelidir.

Bu kurallar işe yarıyor mu? Bir yapı anlamsal olarak ne anlama gelir?


247
System.Drawing.Rectanglebu kuralların üçünü de ihlal eder.
ChrisW

4
C # ile yazılmış birkaç ticari oyun var, nokta optimize edilmiş kod için kullanıldıkları
BlackTigerX

25
Birlikte gruplandırmak istediğiniz küçük değer türleri koleksiyonlarınız olduğunda yapılar daha iyi performans sağlar. Bu, oyun programlamasında her zaman olur, örneğin, 3D modeldeki bir tepe noktası bir pozisyona, doku koordinatına ve normal olacak, ayrıca genellikle değişmez olacaktır. Tek bir modelin birkaç bin köşesi olabilir veya bir düzine olabilir, ancak yapılar bu kullanım senaryosunda genel olarak daha az ek yük sağlar. Bunu kendi motor tasarımımla doğruladım.
Chris D.21


4
@ChrisW Görüyorum, ancak bu değerler bir "tek" değer olan bir dikdörtgeni temsil etmiyor mu? Vector3D veya Color gibi, onlar da içindeki birkaç değerdir, ancak bence tek değerleri temsil ediyorlar mı?
Marson Mao

Yanıtlar:


603

OP tarafından atıfta bulunulan kaynağın güvenilirliği var ... ama Microsoft'a ne dersiniz - yapı kullanımına ilişkin duruş nedir? Microsoft'tan fazladan öğrenmeye çalıştım ve işte bulduğum şey:

Tür örnekleri küçük ve genellikle kısa ömürlü ise veya genellikle diğer nesnelere gömülmüşse, sınıf yerine bir yapı tanımlamayı düşünün.

Tür aşağıdaki özelliklerin hepsine sahip olmadığı sürece bir yapı tanımlamayın:

  1. Mantıksal olarak ilkel tiplere (tamsayı, çift vb.) Benzer tek bir değeri temsil eder.
  2. 16 bayttan küçük bir örnek boyutuna sahiptir.
  3. Değişmez.
  4. Sık sık kutsanması gerekmeyecek.

Microsoft sürekli olarak bu kuralları ihlal ediyor

Tamam, yine de # 2 ve # 3. Sevgili sözlüğümüzün 2 iç yapısı vardır:

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

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

* Referans Kaynağı

'JonnyCantCode.com' kaynağı 4'te 3 elde etti - # 4 muhtemelen sorun olmayacağından oldukça affedilebilir. Kendinizi bir yapıya bürünmüş bulursanız, mimarinizi yeniden düşünün.

Microsoft'un neden bu yapıları kullanacağına bakalım:

  1. Her yapı Entryve Enumeratortek değerleri temsil eder.
  2. hız
  3. Entryhiçbir zaman Dictionary sınıfının dışında bir parametre olarak iletilmez. Daha ayrıntılı araştırmalar, IEnumerable'ın uygulanmasını tatmin etmek için Dictionary'nin Enumeratorbir numaralandırıcı her istendiğinde kopyaladığı yapıyı kullandığını anlamlıdır.
  4. Dictionary sınıfının içinde. Enumeratorherkese açıktır, çünkü Sözlük numaralandırılabilir ve IEnumerator arabirimi uygulamasına eşit erişilebilirliğe sahip olmalıdır - örn. IEnumerator getter.

Güncelleme - Ek olarak, bir yapı - Enumerator'ın yaptığı gibi - bir arabirim uyguladığında ve bu uygulanan türe verildiğinde, yapının bir referans türü haline geldiğini ve yığına taşındığını fark edin. İç Sözlük sınıfına, Listeleyicisi olduğu hala bir değer türü. Ancak, bir yöntem çağrılır çağrılmaz GetEnumerator()bir başvuru türü IEnumeratordöndürülür.

Burada görmediğimiz, yapıları değişmez tutmak veya yalnızca 16 bayt veya daha küçük bir örnek boyutunu korumak için herhangi bir girişim veya kanıt kanıtıdır:

  1. Yukarıda bildirildi yapılar hiçbir şey readonly- değil değişmez
  2. Bu yapının boyutu 16 baytın üzerinde olabilir
  3. Entrybelirsiz bir ömrü vardır (mesafede Add()için Remove(), Clear()ya da çöp toplama);

Ve ... 4. Her iki yapı da hepimiz bildiğimiz TKey ve TValue'yu referans türleri olma yeteneğine sahiptir (ilave bonus bilgisi)

Karma anahtarlar buna rağmen, kısmen hızlıdır çünkü bir yapıyı örneklemek bir referans türünden daha hızlıdır. Burada, Dictionary<int, int>art arda artan tuşları ile 300.000 rasgele tamsayı depolayan bir var.

Kapasite: 312874
Boyut: 2660827 bayt
Tamamlandı Yeniden Boyutlandır: 5ms
Toplam doldurma süresi: 889ms

Kapasite : dahili dizi yeniden boyutlandırılmadan önce kullanılabilecek öğe sayısı.

MemSize : sözlüğü bir MemoryStream içine serileştirerek ve bir bayt uzunluğu alarak (amacımız için yeterince doğru) belirlenir.

Tamamlandı Yeniden Boyutlandır : dahili dizinin 150862 öğeden 312874 öğeye yeniden boyutlandırılması için geçen süre. Her öğenin sırayla kopyalandığını anladığınızda Array.CopyTo(), bu çok perişan değildir.

Doldurulacak toplam süre : günlük kaydı ve OnResizekaynağa eklediğim bir olay nedeniyle kuşkusuz eğri ; ancak, işlem sırasında 15 kez yeniden boyutlandırırken 300k tamsayıları doldurmak yine de etkileyicidir. Sadece meraktan, kapasiteyi zaten biliyorsam toplam doldurma süresi ne olurdu? 13ms

Şimdi, ya Entrybir sınıf olsaydı ? Bu zamanlar veya metrikler gerçekten çok farklı mıdır?

Kapasite: 312874
Boyut: 2660827 byte
Tamamlandı Yeniden Boyutlandır: 26ms
Toplam doldurma süresi: 964ms

Açıkçası, büyük fark yeniden boyutlandırmada. Sözlük Kapasite ile başlatılırsa herhangi bir fark var mı? Endişelenmek için yeterli değil ... 12ms .

Ne olur, çünkü Entrybir yapı olduğundan , bir referans türü gibi başlatma gerektirmez. Bu, değer türünün hem güzelliği hem de çetesi. EntryReferans türü olarak kullanmak için aşağıdaki kodu eklemek zorunda kaldım:

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

Her dizi öğesini Entrybir başvuru türü olarak başlatmamın nedeni MSDN: Structure Design'da bulunabilir . Kısacası:

Bir yapı için varsayılan bir yapıcı sağlamayın.

Bir yapı varsayılan kurucuyu tanımlarsa, yapının dizileri oluşturulduğunda, ortak dil çalışma zamanı her dizi öğesinde varsayılan kurucuyu otomatik olarak yürütür.

C # derleyicisi gibi bazı derleyiciler yapıların varsayılan kuruculara sahip olmasına izin vermez.

Aslında oldukça basit ve Asimov'un Üç Robotik Yasası'ndan ödünç alacağız :

  1. Yapının kullanımı güvenli olmalıdır
  2. Kural 1'i ihlal etmediği sürece yapı işlevini verimli bir şekilde yerine getirmelidir
  3. Kural # 1 kuralını yerine getirmek için imha edilmesi gerekmedikçe, yapı kullanım sırasında bozulmadan kalmalıdır.

... bundan ne alacağız : kısacası değer türlerinin kullanımından sorumlu olmak. Hızlı ve etkilidirler, ancak düzgün bir şekilde muhafaza edilmezlerse (yani kasıtsız kopyalar) birçok beklenmedik davranışa neden olabilirler.


8
Microsoft kurallarına gelince, değişmezlik hakkındaki kural, parçalı olarak değiştirilebilen değer semantiğinin yararlı olabilmesine rağmen, davranışlarının referans türlerinden farklı olacağı şekilde değer türlerinin kullanımını caydırmak için tasarlanmış gibi görünmektedir . Parçalı olarak değiştirilebilir bir türe sahip olmak, çalışmayı kolaylaştırırsa ve türün depolama konumlarının mantıksal olarak birbirinden ayrılması gerekiyorsa, türün "değiştirilebilir" bir yapı olması gerekir.
supercat


2
Microsoft'un türlerinin çoğunun bu kuralları ihlal etmesi, bu türlerle ilgili bir sorunu temsil etmez, aksine kuralların tüm yapı türleri için geçerli olmaması gerektiğini gösterir. Bir yapı [ Decimalveya ile olduğu gibi DateTime] tek bir varlığı temsil ediyorsa, diğer üç kurala uymayacaksa, bir sınıfla değiştirilmelidir. Bir yapı sabit bir değişkenler koleksiyonuna sahipse, her biri kendi türü için geçerli olabilecek herhangi bir değeri tutabilir [örneğin Rectangle], o zaman bazıları "tek değerli" yapılara aykırı olan farklı kurallara uymalıdır. .
Supercat

4
@IAbstract: Bazı insanlar Dictionarygiriş türünü yalnızca dahili bir tür olarak, performansın anlambilimden veya başka bir mazeretten daha önemli olduğu gerekçesiyle haklı çıkarırdı . Demek istediğim, bir türün Rectangleiçeriğinin ayrı ayrı düzenlenebilir alanlar olarak gösterilmesi gerektiği için değil, çünkü performans faydaları ortaya çıkan anlamsal kusurlardan daha ağır basar, ancak tür anlamsal olarak sabit bir bağımsız değerler kümesini temsil ettiğinden ve değişebilir yapı hem daha performanslı ve anlamsal olarak üstün .
supercat

2
@supercat: Katılıyorum ... ve cevabımın asıl amacı, 'kılavuz ilkelerin' oldukça zayıf olması ve yapıların, davranışların tam bilgisi ve anlayışıyla kullanılması gerektiğiydi.
Değişken

155

Ne zaman:

  1. polimorfizme gerek yok,
  2. değer semantiği istemek ve
  3. yığın tahsisinden ve ilişkili çöp toplama yükünden kaçınmak istersiniz.

Bununla birlikte, uyarılar, yapıların (keyfi olarak büyük) aktarılmasının sınıf referanslarından (genellikle bir makine sözcüğü) daha pahalı olduğu için, sınıflar pratikte daha hızlı olabilir.


1
Bu sadece bir "ihtar". Ayrıca (Guid)null, diğer şeylerin yanı sıra , değer türlerinin ve ( örneğin, bir başvuru türüne boş bırakılması uygundur) gibi durumların "kaldırılmasını" da göz önünde bulundurmalıdır .

1
daha pahalı C / C ++? C ++ önerilen yolu değeri nesneleri geçmektir
iyon Todirel

@IonTodirel Performans yerine bellek güvenliği nedeniyle değil miydi? Bu her zaman bir değiş tokuştur, ancak yığın tarafından 32 B'yi geçmek her zaman (B) kayıtla 4 B referansını geçmekten daha yavaş olacaktır. Bununla birlikte , "değer / referans" kullanımının C # ve C ++ 'da biraz farklı olduğuna dikkat edin - bir nesneye referans ilettiğinizde, bir referans geçiyor olsanız bile yine de değere göre geçiyorsunuz ( referansın bir referansını değil, referansın değerini geçerek). Değer semantiği değil , teknik olarak "by-pass-value".
Luaan

@Luaan Kopyalama maliyetlerin sadece bir yönüdür. İşaretçi / referans nedeniyle ekstra dolaylı erişim de erişim başına maliyettir. Bazı durumlarda yapı hareket ettirilebilir ve bu nedenle kopyalanması bile gerekmez.
Onur

@Onur bu ilginç. Kopyalamadan nasıl "taşınır"? Asm "mov" komutunun aslında "hareket" olmadığını düşündüm. Kopyalar.
Winger Sendon

148

Orijinal yayında verilen kurallara katılmıyorum. İşte kurallarım:

1) Dizilerde saklandığında performans için yapılar kullanırsınız. (ayrıca bkz. Yapılar cevap ne zaman? )

2) Yapılandırılmış verileri C / C ++ 'a / C +' dan geçiren kodda onlara ihtiyacınız var

3) Gerek duymadıkça yapıları kullanmayın:

  • Ödev altında ve argüman olarak geçerken beklenmedik davranışlara yol açabilecek "normal nesnelerden" ( referans türleri ) farklı davranırlar ; bu, koda bakan kişi bir yapıyla uğraştığını bilmiyorsa özellikle tehlikelidir.
  • Miras alınamazlar.
  • Yapıları argüman olarak iletmek sınıflardan daha pahalıdır.

4
+1 Evet, tamamen # 1'e katılıyorum ( görüntüler, vb. Şeylerle uğraşırken bu büyük bir avantajdır) ve "normal nesnelerden" farklı olduklarına işaret etmek için ve mevcut bilgi dışında bunu bilmenin bir yolu var veya türün kendisini incelemek. Ayrıca, bir yapı türüne null değer veremezsiniz :-) Bu aslında neredeyse çekirdek olmayan değer türleri için bazı 'Macarca' veya değişken bildirim sitesinde zorunlu 'struct' anahtar kelimesi olmasını istediğim bir durumdur. .

@pst: Bir şeyin bir şey olduğunu bilmek zorunda olduğu doğrudur, structnasıl davranacağını bilmek için, ancak eğer bir şey structaçıkta kalan alanlarla bir ise, hepsi bilmelidir. Bir nesne açık alan-yapı türünün bir özelliğini ortaya çıkarırsa ve kod bu yapıyı bir değişkene okur ve değiştirirse, bu tür bir eylem, yapı yazılmadığı sürece veya yapılana kadar özelliği okunan nesneyi etkilemeyeceğini güvenli bir şekilde tahmin edebilir. geri. Buna karşılık, özellik değiştirilebilir bir sınıf türü olsaydı, onu okumak ve değiştirmek temel nesneyi beklendiği gibi güncelleyebilir, ancak ...
supercat

... aynı zamanda hiçbir şeyi değiştirmeyebilir ya da değiştirmeyi amaçlamayan nesneleri değiştirebilir ya da bozabilir. Anlambilimleri "bu değişkeni istediğiniz gibi değiştirin; değişiklikleri açıkça bir yerde saklayana kadar hiçbir şey yapmayacak" koduna sahip olmak, "Herhangi bir sayı ile paylaşılan bir nesneye referans alıyorsunuz" koduna sahip olmaktan daha net görünüyor diğer referanslardan veya hiç paylaşılmayabilir; değiştirirseniz ne olacağını bilmek için bu nesneye başka kimin referansları olabileceğini anlamanız gerekir. "
supercat

# 1 ile tespit edin. Yapılarla dolu bir liste, nesne referanslarıyla dolu bir listeden (doğru boyut yapısı için) L1 / L2 önbelleklerine çok daha ilgili verileri sıkıştırabilir.
Matt Stephenson

2
Kalıtım nadiren iş için doğru araçtır ve profil oluşturmadan performans hakkında çok fazla düşünmek kötü bir fikirdir. İlk olarak, yapılar referans olarak geçirilebilir. İkincisi, referans ya da değere göre geçmek nadiren önemli bir performans sorunudur. Son olarak, bir sınıf için yapılması gereken ek yığın tahsisi ve çöp toplama işlemlerini hesaba katmıyorsunuz. Şahsen, şeyler olarak düz eski veri ve sınıfların olarak yapılar görmeyi tercih ederim yapmak siz de yapılar üzerinde yöntemleri tanımlayabilir rağmen şeyler (nesneler).
weberc2

88

Referans semantiğe karşılık değer semantiği istediğinizde bir yapı kullanın.

Düzenle

İnsanların bunu neden düşürdüğünden emin değilim, ancak bu geçerli bir nokta ve op sorusunu açıklamadan önce yapıldı ve bir yapının en temel temel nedenidir.

Referans semantiğe ihtiyacınız varsa bir yapıya değil bir sınıfa ihtiyacınız vardır.


13
Bunu herkes biliyor. Görünüşe göre "yapı bir değer türü" cevabından fazlasını arıyor.
TheSmurf

21
Bu en temel durumdur ve bu yazıyı okuyan ve bunu bilmeyen herkes için belirtilmelidir.
JoshBerke

3
Bu cevabın doğru olmadığı değil; belli ki öyle. Asıl mesele bu değil.
TheSmurf

55
@Josh: Henüz bilmeyen biri için, bunun yetersiz bir cevap olduğunu söylemek, çünkü bunun ne anlama geldiğini bilmemesi de muhtemeldir.
TheSmurf

1
Ben sadece aşağı indirdim çünkü diğer cevaplardan biri üstte olması gerektiğini düşünüyorum - "Yönetilmeyen kod ile birlikte çalışma için, aksi takdirde kaçının" yazan herhangi bir cevap.
Daniel Earwicker

59

"Bir değerdir" cevabını ek olarak, kullanarak.Gerçeği için belirli bir senaryo ne zaman olduğunu biliyorum çöp toplama sorunlara neden oluyorsa bir veri setine sahip olduğunu ve nesnelerin bir sürü var. Örneğin, büyük bir Kişi örnekleri listesi / dizisi. Buradaki doğal metafor bir sınıftır, ancak çok sayıda uzun ömürlü Kişi örneğiniz varsa, GEN-2'yi tıkayabilir ve GC tezgahlarına neden olabilirler. Senaryo emirleri Eğer burada bir potansiyel yaklaşım bir dizi Kişi ait (değil listesi) kullanmaktır yapılar , yani Person[]. Şimdi, GEN-2'de milyonlarca nesneye sahip olmak yerine, LOH'da tek bir parça var (burada dizeler vb. Yok - yani herhangi bir referans olmadan saf bir değer var). Bunun çok az GC etkisi vardır.

Veriler bir yapı için büyük olasılıkla büyük olduğundan ve her zaman yağ değerlerini kopyalamak istemediğiniz için bu verilerle çalışmak gariptir. Bununla birlikte, doğrudan bir dizide erişmek yapıyı kopyalamaz - yerinde (kopyalayan bir liste dizinleyicisinin aksine). Bu, dizinlerle çok çalışma anlamına gelir:

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

Değerlerin kendilerini sabit tutmanın burada yardımcı olacağını unutmayın. Daha karmaşık bir mantık için by-ref parametresine sahip bir yöntem kullanın:

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

Yine, bu yerinde - değeri kopyalamıyoruz.

Çok spesifik senaryolarda, bu taktik çok başarılı olabilir; ancak, yalnızca ne yaptığınızı ve nedenini biliyorsanız, denenmesi gereken oldukça gelişmiş bir scernario'dur. Burada varsayılan bir sınıf olacaktır.


+1 İlginç cevap. Böyle bir yaklaşımın kullanılmasıyla ilgili gerçek dünya anekdotlarını paylaşmak ister misiniz?
Jordão

@Jordao, ancak mobil cihazlarda google için arama: + gravell + "GC tarafından saldırı"
Marc Gravell

1
Çok teşekkürler. Burada buldum .
Jordão

2
@MarcGravell Neden bahsettiniz: bir dizi kullan (liste değil) ? ListBence Arrayarka plan sahneleri kullanıyor . Hayır ?
Royi Namir

4
@RoyiNamir Bunu da merak ettim, ama cevabın Marc'ın cevabının ikinci paragrafında olduğuna inanıyorum. "Bununla birlikte, bir dizide doğrudan erişmek yapıyı kopyalamaz - yerinde (kopyalayan bir liste dizinleyicisinin aksine)."
user1323245

40

Gönderen C # Dili şartname :

1.7 Yapılar

Sınıflar gibi, yapılar da veri üyeleri ve işlev üyeleri içerebilen veri yapılarıdır, ancak sınıfların aksine, yapılar değer türleridir ve yığın tahsisi gerektirmez. Bir yapı türünün bir değişkeni doğrudan yapının verilerini depolarken, sınıf türünün bir değişkeni dinamik olarak ayrılmış bir nesneye bir referans depolar. Yapı türleri, kullanıcı tarafından belirtilen kalıtımı desteklemez ve tüm yapı türleri, dolaylı olarak tür nesnesinden miras alır.

Yapılar özellikle değer semantiğine sahip küçük veri yapıları için kullanışlıdır. Karmaşık sayılar, koordinat sistemindeki noktalar veya sözlükteki anahtar / değer çiftleri yapıların iyi örnekleridir. Küçük veri yapıları için sınıflar yerine yapıların kullanılması, bir uygulamanın gerçekleştirdiği bellek ayırma sayısında büyük bir fark yaratabilir. Örneğin, aşağıdaki program 100 noktadan oluşan bir dizi oluşturur ve başlatır. Nokta bir sınıf olarak uygulandığında, biri dizi ve diğeri 100 öğe için 101 ayrı nesne başlatılır.

class Point
{
   public int x, y;

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

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

Bir alternatif Point'i bir yapı yapmaktır.

struct Point
{
   public int x, y;

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

Şimdi, yalnızca bir nesne (dizi için olan) başlatılır ve Point örnekleri dizide satır içinde depolanır.

Yapısal kurucular yeni operatörle çağrılır, ancak bu, belleğin tahsis edildiği anlamına gelmez. Bir nesneyi dinamik olarak ayırmak ve ona bir başvuru döndürmek yerine, bir yapı 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.

Sınıflarla, iki değişkenin aynı nesneyi referans alması ve dolayısıyla bir değişken üzerindeki işlemlerin diğer değişken tarafından başvurulan nesneyi etkilemesi mümkündür. Yapılarla, değişkenlerin her birinin kendi veri kopyası vardır ve birindeki işlemlerin diğerini etkilemesi mümkün değildir. Örneğin, aşağıdaki kod parçası tarafından üretilen çıktı, Point'in bir sınıf mı yoksa bir yapı mı olduğuna bağlıdır.

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

Point bir sınıfsa, çıkış 20'dir, çünkü a ve b aynı nesneye başvurur. Nokta bir yapı ise çıktı 10'dur, çünkü a'dan b'ye atama değerin bir kopyasını oluşturur ve bu kopya sonraki balta atamasından etkilenmez

Önceki örnek, yapıların iki sınırlamasını vurgular. İlk olarak, bir yapının tamamını kopyalamak, genellikle bir nesne referansını kopyalamaktan daha az etkilidir, bu nedenle atama ve değer parametresi geçirme, yapılarda referans türlerine göre daha pahalı olabilir. İkincisi, ref ve out parametreleri dışında, birtakım durumlarda kullanımlarını dışlayan yapılara referanslar oluşturmak mümkün değildir.


4
Yapılara yapılan göndermelerin devam edememesi bazen bir sınırlama olsa da, aynı zamanda çok kullanışlı bir özelliktir. .Net'in en önemli zayıf noktalarından biri, sonsuza kadar bu nesnenin denetimini kaybetmeden, değişebilir bir nesneye bir referans kodunu dışa aktarmanın iyi bir yolu olmamasıdır. Bunun aksine, refmutasyon geçirebilen bir yapıya güvenli bir şekilde bir dış yöntem a verebilir ve dış yöntemin üzerinde gerçekleştireceği tüm mutasyonların geri dönmeden önce yapılacağını bilir. Çok kötü .net geçici parametreler ve işlev dönüş değerleri kavramına sahip değil, çünkü ...
Supercat

4
... bu, geçirilen yapıların avantajlı semantiğinin refsınıfsal nesnelerle elde edilmesini sağlayacaktır. Temel olarak, yerel değişkenler, parametreler ve işlev döndürme değerleri kalıcı (varsayılan), döndürülebilir veya geçici olabilir. Kuralların geçici şeyleri mevcut kapsamı aşacak herhangi bir şeye kopyalaması yasaklanmış olacaktır. Geri döndürülebilir şeyler, bir işlevden döndürülebilmeleri dışında geçici şeyler gibi olurdu. Bir işlevin dönüş değeri, "döndürülebilir" parametrelerinden herhangi biri için geçerli olan en sıkı kısıtlamalara bağlı olacaktır.
Supercat

34

Yapılar, verilerin atomik temsili için iyidir, burada adı geçen veriler kod ile birden çok kez kopyalanabilir. Bir nesneyi klonlamak genel olarak bir yapıyı kopyalamaktan daha pahalıdır, çünkü hafızanın tahsis edilmesini, yapıcıyı çalıştırmayı ve iş bittiğinde depolamayı / çöp toplamayı içerir.


4
Evet, ancak büyük yapılar sınıf referanslarından daha pahalı olabilir (yöntemlere geçerken).
Alex

27

İşte temel bir kural.

  • Tüm üye alanları değer türüyse bir yapı oluşturun .

  • Herhangi bir üye alanı başvuru tipiyse bir sınıf oluşturun . Bunun nedeni, başvuru türü alanının yığın tahsisine zaten ihtiyacı olmasıdır.

Exmaples

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

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

3
Gibi stringdeğişmeyen referans türleri anlamsal olarak değerlere eşdeğerdir ve değişmez bir nesneye bir referansın bir alanda depolanması yığın tahsisi gerektirmez. Maruz kalan halka alanları ile bir yapı ve açıkta kalan halka alanları ile bir sınıf nesnesi arasındaki fark kod dizisi verilen olmasıdır var q=p; p.X=4; q.X=5;, p.Xeğer değeri 4 olacak abir sınıf tipi ise 5 bir yapı türü ve. Eğer kişi türün üyelerini uygun şekilde değiştirebilmek istiyorsa, değişikliklerin qetkilenmesini isteyip istemediğine bağlı olarak 'sınıf' veya 'yapı' seçilmelidir p.
supercat

Evet, başvuru değişkeninin yığın üzerinde olacağına katılıyorum ama atıfta bulunulan nesne yığın üzerinde var olacaktır. Yapılar ve sınıflar farklı bir değişkene atandığında farklı davranmasına rağmen, bu güçlü bir karar faktörü olduğunu düşünmüyorum.
Usman Zafar

Değişken yapılar ve değişebilir sınıflar tamamen farklı davranırlar; biri doğruysa, diğeri büyük olasılıkla yanlış olacaktır. Bir yapı mı yoksa sınıf mı kullanılacağını belirleyen davranışın nasıl belirleyici bir faktör olmayacağından emin değilim.
supercat

Bunun güçlü bir karar faktörü olmadığını söyledim çünkü çoğu zaman bir sınıf veya yapı oluştururken nasıl kullanılacağından emin değilsiniz. Böylece, işlerin tasarım açısından nasıl daha mantıklı olduğuna odaklanırsınız. Neyse ben hiç bir yapı bir başvuru değişkeni içeren .NET kitaplığında tek bir yerde görmedim.
Usman Zafar

1
Yapı türü , her zaman bir sınıf türü olan ArraySegment<T>a'yı T[]içerir. Yapı tipi KeyValuePair<TKey,TValue>genellikle genel parametre olarak sınıf türleriyle kullanılır.
supercat

19

İlk olarak: Birlikte çalışma senaryoları veya bellek düzenini belirtmeniz gerektiğinde

İkincisi: Veriler zaten bir referans işaretçisi ile hemen hemen aynı boyutta olduğunda.


17

Genellikle PInvoke için - StructLayoutAttribute kullanarak bellek düzenini açıkça belirtmek istediğiniz durumlarda bir "struct" kullanmanız gerekir .

Düzenleme: Yorum, StructLayoutAttribute ile sınıf veya struct kullanabileceğinizi gösterir ve bu kesinlikle doğrudur. Pratikte, genellikle bir yapı kullanırsınız - yığına karşı yığına tahsis edilir, bu da yönetilmeyen bir yöntem çağrısına sadece bir argüman geçiriyorsanız mantıklıdır.


5
StructLayoutAttribute, yapılara veya sınıflara uygulanabilir, böylece bu yapıları kullanmak için bir neden değildir.
Stephen Martin

Yönetilmeyen bir yöntem çağrısına yalnızca bir argüman iletiyorsanız neden mantıklı geliyor?
David Klempfner

16

Herhangi bir ikili iletişim formatını paketlemek veya paketten çıkarmak için yapılar kullanıyorum. Bu, diske okuma veya yazma, DirectX köşe listeleri, ağ protokolleri veya şifreli / sıkıştırılmış verilerle uğraşmayı içerir.

Listelediğiniz üç yönerge bu bağlamda benim için yararlı olmamıştır. Belirli Bir Düzende dört yüz baytlık bir şeyler yazmam gerektiğinde, dört yüz baytlık bir yapı tanımlayacağım ve bunu olması gereken alakasız değerlerle dolduracağım ve gidiyorum o zaman en mantıklı olanı kurmak. (Tamam, dört yüz bayt oldukça garip olurdu - ama yaşamak için Excel dosyaları yazarken, her yerde yaklaşık kırk bayta kadar olan yapılarla uğraşıyordum, çünkü BIFF kayıtlarının bazıları bu kadar büyük.


Yine de bunun için bir referans türü kullanamaz mısınız?
David Klempfner

15

PInvoke amaçları için doğrudan çalışma zamanı ve diğerleri tarafından kullanılan değer türleri dışında, değer türlerini yalnızca 2 senaryoda kullanmanız gerekir.

  1. Kopya semantiğine ihtiyacınız olduğunda.
  2. Otomatik başlatmaya ihtiyacınız olduğunda, normalde bu tür dizilerde.

# 2 , .Net koleksiyon sınıflarında
yapısal

Sınıf türünde bir depolama konumu oluştururken yapılacak ilk şey, bu türün yeni bir örneğini oluşturmak, bu konumda ona bir referans depolamak ve referansı hiçbir zaman başka bir yere kopyalamaz veya üzerine yazmazsa, ve sınıf aynı şekilde davranırdı. Yapılar, tüm alanları bir örnekten diğerine kopyalamak için uygun standart bir yola sahiptir ve bir sınıfın referansını asla çoğaltmayacağı durumlarda ( thisyöntemlerini çağırmak için kullanılan geçici parametre hariç) genellikle daha iyi performans sunar ; sınıfları referansları çoğaltmaya izin verir.
supercat

13

.NET destekler value typesve reference types(Java'da yalnızca referans türlerini tanımlayabilirsiniz). reference typesYönetilen öbekte tahsis edilen örnekler ve bunlara olağanüstü referanslar olmadığında toplanan çöplerdir. Örneklerini value types, diğer taraftan, tahsis edilir stackve dolayısıyla ayrılan bellek onların kapsamı uçları olan en kısa sürede iadesi. Ve elbette, value typesdeğere ve reference typesreferansa göre geçin. System.String dışındaki tüm C # ilkel veri türleri değer türleridir.

Yapı üzerinde sınıf ne zaman kullanılır?

C # ' structsde value types, sınıflar vardır reference types. enumAnahtar kelimeyi ve structanahtar kelimeyi kullanarak C # 'da değer türleri oluşturabilirsiniz . Bir value typeyerine kullanmak reference type, yönetilen yığın üzerinde daha az nesneye neden olur, bu da çöp toplayıcı (GC) üzerinde daha az yük, daha az sıklıkta GC döngüleri ve sonuç olarak daha iyi performans sağlar. Ancak, value typesonların dezavantajları var. Büyük bir structşeyden geçmek kesinlikle referans almaktan daha pahalıdır, bu bariz bir problemdir. Diğer sorun ise ile ilişkili genel gider boxing/unboxing. Ne boxing/unboxinganlama geldiğini merak ediyorsanız , boxingveunboxing. Performansın yanı sıra, değer semantiğine sahip türlere ihtiyaç duyduğunuz zamanlar vardır; bu, sahip olduğunuz reference typestek şey varsa uygulamak çok zor (veya çirkin) olacaktır . value typesYalnızca arraysbu tür metinlerde, kopya semantiğine veya otomatik başlatmaya ihtiyacınız olduğunda kullanmalısınız .


Küçük yapıları kopyalamak veya değere göre geçmek, sınıf referansını kopyalamak veya geçirmek veya yapıları geçmek kadar ucuzdur ref. Herhangi bir boyut yapısını refmaliyetle geçirmek, sınıf referansını değere göre geçirmekle aynıdır. Herhangi bir boyut yapısını kopyalamak veya değere göre geçmek, bir sınıf nesnesinin defansif bir kopyasını yapmak ve referansı saklamak veya iletmekten daha ucuzdur. Sınıflar değişmez olduğunda (defansif kopyalamayı önlemek için) büyük zaman sınıfları değerleri saklamak için yapılan yapılardan daha iyidir (oluşturulan kopyaların önlenmesi için) ve oluşturulan her örnek çok
geçecek

... (2) çeşitli nedenlerden dolayı bir yapı sadece kullanılamayacağı zaman [örneğin, bir ağaç gibi bir şey için iç içe referanslar kullanması gerektiğinden veya polimorfizm gerektiğinden]. Değer türlerini kullanırken, genellikle belirli bir nedenin bulunmadığı alanları doğrudan göstermemesi gerektiğini unutmayın (oysa çoğu sınıf türü alanı özelliklerin içine sarılmalıdır). Değişken değer türlerinin sözde "kötülükleri" nin çoğu, özelliklerde alanların gereksiz yere sarılmasından kaynaklanır (örneğin, bazı derleyiciler, bazen bir özellik salt okunur bir yapı üzerinde bir özellik ayarlayıcısını çağırmasına izin verirken ...
supercat

... doğru olanı yapın, tüm derleyiciler bu tür yapılara doğrudan alan koyma girişimlerini düzgün bir şekilde reddeder; derleyiciler reddetmek sağlamanın en iyi yolu readOnlyStruct.someMember = 5;yapmak değil someMembersalt okunur özelliğini, ama bunun yerine bir saha yapmak.
Supercat

12

Bir yapı , bir değer türüdür. Yeni bir değişkene yapı atarsanız, yeni değişken orijinalin bir kopyasını içerir.

public struct IntStruct {
    public int Value {get; set;}
}

Aşağıdakilerin hariç tutulması, bellekte depolanan yapının 5 örneğiyle sonuçlanır:

var struct1 = new IntStruct() { Value = 0 }; // original
var struct2 = struct1;  // A copy is made
var struct3 = struct2;  // A copy is made
var struct4 = struct3;  // A copy is made
var struct5 = struct4;  // A copy is made

// NOTE: A "copy" will occur when you pass a struct into a method parameter.
// To avoid the "copy", use the ref keyword.

// Although structs are designed to use less system resources
// than classes.  If used incorrectly, they could use significantly more.

Bir sınıf , bir referans türüdür. Bir sınıfı yeni bir değişkene atadığınızda, değişken orijinal sınıf nesnesine bir başvuru içerir.

public class IntClass {
    public int Value {get; set;}
}

Aşağıdakilerin hariç tutulması, bellekteki sınıf nesnesinin yalnızca bir örneğiyle sonuçlanır.

var class1 = new IntClass() { Value = 0 };
var class2 = class1;  // A reference is made to class1
var class3 = class2;  // A reference is made to class1
var class4 = class3;  // A reference is made to class1
var class5 = class4;  // A reference is made to class1  

Yapılar kod hatası olasılığını artırabilir. Bir değer nesnesine değiştirilebilir bir başvuru nesnesi gibi davranılırsa, yapılan değişiklikler beklenmedik bir şekilde kaybolduğunda bir geliştirici şaşırtabilir.

var struct1 = new IntStruct() { Value = 0 };
var struct2 = struct1;
struct2.Value = 1;
// At this point, a developer may be surprised when 
// struct1.Value is 0 and not 1

12

Sayılarda "yapı" yararını daha iyi anlamak için BenchmarkDotNet ile küçük bir karşılaştırma yaptım . Ben dizi (veya liste) yapıları (veya sınıfları) üzerinden döngü test ediyorum. Bu dizileri veya listeleri oluşturmak, karşılaştırmanın kapsamı dışındadır - "sınıfın" daha ağır olduğu, daha fazla bellek kullanacağı ve GC'yi içereceği açıktır.

Sonuç olarak: LINQ ve gizli yapılara dikkat edin, boks / kutudan çıkarma ve mikrooptimizasyon yapılarını kesinlikle dizilerde kullanın.

Not: Yapıyı / sınıfı çağrı yığını üzerinden geçirme ile ilgili bir başka ölçüt de https://stackoverflow.com/a/47864451/506147

BenchmarkDotNet=v0.10.8, OS=Windows 10 Redstone 2 (10.0.15063)
Processor=Intel Core i5-2500K CPU 3.30GHz (Sandy Bridge), ProcessorCount=4
Frequency=3233542 Hz, Resolution=309.2584 ns, Timer=TSC
  [Host] : Clr 4.0.30319.42000, 64bit RyuJIT-v4.7.2101.1
  Clr    : Clr 4.0.30319.42000, 64bit RyuJIT-v4.7.2101.1
  Core   : .NET Core 4.6.25211.01, 64bit RyuJIT


          Method |  Job | Runtime |      Mean |     Error |    StdDev |       Min |       Max |    Median | Rank |  Gen 0 | Allocated |
---------------- |----- |-------- |----------:|----------:|----------:|----------:|----------:|----------:|-----:|-------:|----------:|
   TestListClass |  Clr |     Clr |  5.599 us | 0.0408 us | 0.0382 us |  5.561 us |  5.689 us |  5.583 us |    3 |      - |       0 B |
  TestArrayClass |  Clr |     Clr |  2.024 us | 0.0102 us | 0.0096 us |  2.011 us |  2.043 us |  2.022 us |    2 |      - |       0 B |
  TestListStruct |  Clr |     Clr |  8.427 us | 0.1983 us | 0.2204 us |  8.101 us |  9.007 us |  8.374 us |    5 |      - |       0 B |
 TestArrayStruct |  Clr |     Clr |  1.539 us | 0.0295 us | 0.0276 us |  1.502 us |  1.577 us |  1.537 us |    1 |      - |       0 B |
   TestLinqClass |  Clr |     Clr | 13.117 us | 0.1007 us | 0.0892 us | 13.007 us | 13.301 us | 13.089 us |    7 | 0.0153 |      80 B |
  TestLinqStruct |  Clr |     Clr | 28.676 us | 0.1837 us | 0.1534 us | 28.441 us | 28.957 us | 28.660 us |    9 |      - |      96 B |
   TestListClass | Core |    Core |  5.747 us | 0.1147 us | 0.1275 us |  5.567 us |  5.945 us |  5.756 us |    4 |      - |       0 B |
  TestArrayClass | Core |    Core |  2.023 us | 0.0299 us | 0.0279 us |  1.990 us |  2.069 us |  2.013 us |    2 |      - |       0 B |
  TestListStruct | Core |    Core |  8.753 us | 0.1659 us | 0.1910 us |  8.498 us |  9.110 us |  8.670 us |    6 |      - |       0 B |
 TestArrayStruct | Core |    Core |  1.552 us | 0.0307 us | 0.0377 us |  1.496 us |  1.618 us |  1.552 us |    1 |      - |       0 B |
   TestLinqClass | Core |    Core | 14.286 us | 0.2430 us | 0.2273 us | 13.956 us | 14.678 us | 14.313 us |    8 | 0.0153 |      72 B |
  TestLinqStruct | Core |    Core | 30.121 us | 0.5941 us | 0.5835 us | 28.928 us | 30.909 us | 30.153 us |   10 |      - |      88 B |

Kod:

[RankColumn, MinColumn, MaxColumn, StdDevColumn, MedianColumn]
    [ClrJob, CoreJob]
    [HtmlExporter, MarkdownExporter]
    [MemoryDiagnoser]
    public class BenchmarkRef
    {
        public class C1
        {
            public string Text1;
            public string Text2;
            public string Text3;
        }

        public struct S1
        {
            public string Text1;
            public string Text2;
            public string Text3;
        }

        List<C1> testListClass = new List<C1>();
        List<S1> testListStruct = new List<S1>();
        C1[] testArrayClass;
        S1[] testArrayStruct;
        public BenchmarkRef()
        {
            for(int i=0;i<1000;i++)
            {
                testListClass.Add(new C1  { Text1= i.ToString(), Text2=null, Text3= i.ToString() });
                testListStruct.Add(new S1 { Text1 = i.ToString(), Text2 = null, Text3 = i.ToString() });
            }
            testArrayClass = testListClass.ToArray();
            testArrayStruct = testListStruct.ToArray();
        }

        [Benchmark]
        public int TestListClass()
        {
            var x = 0;
            foreach(var i in testListClass)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestArrayClass()
        {
            var x = 0;
            foreach (var i in testArrayClass)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestListStruct()
        {
            var x = 0;
            foreach (var i in testListStruct)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestArrayStruct()
        {
            var x = 0;
            foreach (var i in testArrayStruct)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestLinqClass()
        {
            var x = testListClass.Select(i=> i.Text1.Length + i.Text3.Length).Sum();
            return x;
        }

        [Benchmark]
        public int TestLinqStruct()
        {
            var x = testListStruct.Select(i => i.Text1.Length + i.Text3.Length).Sum();
            return x;
        }
    }

Listelerde ve benzerlerinde kullanıldığında yapıların neden bu kadar yavaş olduğunu anladınız mı? Bahsettiğiniz gizli boks ve kutudan çıkarma yüzünden mi? Öyleyse neden oluyor?
Marko Grdinic

Dizideki yapıya erişim, yalnızca ek başvuru gerekmediği için daha hızlı olmalıdır. Boks / Kutudan Çıkarma linq için durumdur.
Roman Pokrovskij

10

C # veya diğer .net dillerindeki yapı türleri genellikle sabit boyutlu değer grupları gibi davranması gereken şeyleri tutmak için kullanılır. Yapı tiplerinin yararlı bir yönü, bir yapı tipi örneğin alanlarının, içinde tutulduğu depolama konumunu değiştirerek ve başka bir şekilde değiştirilememesidir. Bir yapıyı herhangi bir alanı mutasyona uğratmanın tek yolunun tamamen yeni bir örnek oluşturmak ve daha sonra yeni örnekteki değerlerle üzerine yazarak hedefin tüm alanlarını değiştirmek için bir yapı ataması kullanmak üzere kodlamak mümkündür, ancak Bir yapı, alanlarının varsayılan olmayan değerlere sahip olduğu bir örnek oluşturmanın bir yolunu sağlamadığı sürece, yapının kendisi değiştirilebilir bir konumda depolanırsa ve depolanırsa, alanlarının tümü değiştirilebilir.

Yapı, özel sınıf türü bir alan içeriyorsa ve kendi üyelerini, sarılmış sınıf nesnesinin yönüne yönlendiriyorsa, temelde bir sınıf türü gibi davranacak şekilde bir yapı türü tasarlamanın mümkün olduğunu unutmayın. Örneğin, bir PersonCollectionkudreti teklif özellikleri SortedByNameve SortedById, a, bir "sabit" referans sahip her ikisi de PersonCollection(bunların yapıcısında grubu) ve uygulanması GetEnumeratorya arayarak creator.GetNameSortedEnumeratorya da creator.GetIdSortedEnumerator. Bu tür yapılar PersonCollection, GetEnumeratoryöntemlerinin farklı yöntemlere bağlı olması dışında, a'ya bir referans gibi davranır PersonCollection. Ayrıca, bir dizinin bir kısmını saran bir yapı da olabilir (örneğin , bir çağrılan , int ve int ArrayRange<T>içeren bir yapı tanımlanabilirT[]ArrOffsetLengthBir endeksli özelliğiyle hangi bir dizin için idx0'dan için Length-1, erişmek istiyorum Arr[idx+Offset]). Ne yazık ki, fooböyle bir yapının salt okunur bir örneğiyse, geçerli derleyici sürümleri, foo[3]+=4;bu tür işlemlerin alanlarına yazma girişiminde bulunup bulunmayacağını belirlemek için hiçbir yolu olmadığı için işlemlere izin vermez foo.

Bir yapıyı, değişken boyutlu bir koleksiyona sahip olan (yapı her kopyalandığında kopyalanacak gibi görünen) bir değer türü gibi davranacak şekilde tasarlamak da mümkündür, ancak bu işi yapmanın tek yolu, struct, onu değiştirebilecek herhangi bir şeye maruz kalacak bir referans tutar. Örneğin, özel bir diziyi tutan ve dizine alınmış "put" yöntemi, içeriği değiştirilen bir öğe dışında orijinaline benzeyen yeni bir dizi oluşturan dizi benzeri bir yapıya sahip olabilir. Ne yazık ki, bu tür yapıların verimli bir şekilde çalışmasını sağlamak biraz zor olabilir. Yapı semantiğinin uygun olabileceği zamanlar olsa da (ör. Dizi benzeri bir koleksiyonu rutine aktarabilme, arayan ve her ikisi de dış kodun koleksiyonu değiştirmeyeceğini bilerek,


10

Hayır - Kurallara tamamen katılmıyorum. Performans ve standardizasyon ile dikkate alınması gereken iyi kılavuzlardır, ancak olasılıklar ışığında değildir.

Yanıtlarda görebileceğiniz gibi, bunları kullanmanın birçok yaratıcı yolu vardır. Dolayısıyla, bu kılavuz ilkelerin her zaman performans ve verimlilik uğruna olması gerekir.

Bu durumda, gerçek dünya nesnelerini daha büyük formlarında temsil etmek için sınıfları, daha kesin kullanımları olan daha küçük nesneleri temsil etmek için yapıları kullanırım. Söylediğin gibi, "daha uyumlu bir bütün." Anahtar kelime uyumlu. Sınıflar daha nesne yönelimli unsurlar olurken, yapılar daha küçük ölçekte de olsa bu özelliklerden bazılarına sahip olabilir. IMO.

Onları yaygın statik özelliklere çok hızlı bir şekilde erişilebilen Treeview ve Listview etiketlerinde çok kullanıyorum. Bu bilgiyi her zaman başka bir yolla almak için mücadele ettim. Örneğin, veritabanı uygulamalarımda, Tablolar, SP'ler, İşlevler veya başka nesnelerim olan bir Ağaç Görünümü kullanıyorum. Yapımı oluşturup dolduruyorum, etikete koyuyorum, çıkarıyorum, seçim verilerini alıyorum vb. Bunu bir sınıfla yapmazdım!

Onları küçük tutmaya çalışıyorum, tek örnek durumlarda kullanıyorum ve değişmelerini önlüyorum. Bellek, tahsis ve performansın farkında olmak ihtiyatlı. Ve test etmek çok gerekli.


Yapılar, hafif değişmez nesneleri temsil etmek için mantıklı bir şekilde kullanılabilir ya da ilişkili fakat bağımsız değişkenlerin sabit setlerini (örneğin bir noktanın koordinatları) temsil etmek için mantıklı olarak kullanılabilir. Bu sayfadaki tavsiye, önceki amaca hizmet etmek üzere tasarlanmış yapılar için iyidir, ancak ikinci amaca hizmet etmek üzere tasarlanmış yapılar için yanlıştır. Şu andaki düşüncem, herhangi bir özel alana sahip yapıların genellikle belirtilen açıklamayı karşılaması gerektiğidir, ancak birçok yapı devletlerinin tamamını kamu alanları aracılığıyla ortaya çıkarmalıdır.
supercat

"3B nokta" türünün belirtimi, tüm durumunun okunabilir üyeler x, y ve z aracılığıyla gösterildiğini gösteriyorsa ve doublebu koordinatlar için herhangi bir değer birleşimiyle bir örnek oluşturmak mümkündür , böyle bir özellik çok iş parçacıklı davranışın bazı ayrıntıları haricinde açıkta kalan bir alan yapısıyla semantik olarak aynı davranırlar (bazı durumlarda değişmez sınıf daha iyi olurken, açıkta kalan alan yapısı diğerlerinde daha iyi olur; "değişmez" bir yapı her durumda daha kötü olmak).
supercat

8

Kuralım

1, her zaman sınıf kullanın;

2, herhangi bir performans sorunu varsa, @IAbstract belirtilen kurallara bağlı olarak yapı için bazı sınıf değiştirmeye çalışın ve sonra bu değişikliklerin performansı artırabilir olmadığını görmek için bir test yapın.


Microsoft'un görmezden geldiği önemli bir kullanım durumu, birinin değişken olarak Foosabit bir bağımsız değerler koleksiyonunu (örneğin bir noktanın koordinatları) kapsüllemek istediğinde, bazen bir grup olarak geçmek isteyip bazen bağımsız olarak değiştirmek isteyeceğidir. Her iki amacı neredeyse basit bir açık alan yapısı olarak (bağımsız değişkenlerin sabit bir koleksiyonu olarak, fatura mükemmel uyuyor) kadar güzel birleştiren sınıfları kullanmak için herhangi bir desen bulamadım.
supercat

1
@supercat: Bunun için Microsoft'u suçlamanın tamamen adil olmadığını düşünüyorum. Burada asıl mesele, nesne yönelimli bir dil olarak C # 'ın sadece çok fazla davranış olmadan verileri ortaya çıkaran düz kayıt türlerine odaklanmamasıdır. C #, örneğin C ++ ile aynı ölçüde çoklu paradigma dili değildir. Varlık dedim de belki C # çok idealist bir dildir, çok az insan saf OOP programlamak inanıyoruz. (Biri için son zamanlarda public readonlytürlerimdeki alanları da açığa çıkarmaya başladım, çünkü salt okunur özellikler oluşturmak neredeyse hiç fayda sağlamayacak kadar fazla iş.)
stakx -

1
@stakx: Bu türlere "odaklanmasına" gerek yok; onları ne oldukları için tanımak yeterli olacaktır. Yapılarda C # ile ilgili en büyük zayıflık, diğer birçok alanda da en büyük problemidir: dil, belirli dönüşümlerin ne zaman uygun olup olmadığını göstermek için yetersiz olanaklar sağlar ve bu tür tesislerin eksikliği talihsiz tasarım kararlarını yönlendirir. Örneğin, "değişken yapılar kötüdür" % 99 dönüm derleyici en kaynaklanıyor MyListOfPoint[3].Offset(2,3);içine var temp=MyListOfPoint[3]; temp.Offset(2,3);... uygulandığında sahte olduğu dönüşümü,
SuperCat

... Offsetyönteme. Bu tür sahte kodları önlemenin uygun yolu, yapıları gereksiz yere değiştirilemez hale getirmemeli, bunun yerine Offsetyukarıda belirtilen dönüşümü yasaklayan bir öznitelikle etiketlenmeye benzer yöntemlere izin verilmelidir. Örtülü sayısal dönüşümler de, yalnızca çağrılmalarının açık olacağı durumlarda geçerli olacak şekilde etiketlenebilseydi çok daha iyi olabilirdi. Eğer aşırı yükler varsa foo(float,float)ve foo(double,double), bir floatve doublegenellikle kullanmaya çalışmanın örtük bir dönüşüm uygulamaması gerektiğini, ancak bunun yerine bir hata olması gerektiğini söyleyebilirim .
supercat

Bir doubledeğerin a'ya doğrudan atanması floatya da bir floatargüman alabilen ancak bir yöntemi kullanmayan bir yönteme aktarılması double, neredeyse her zaman programcının amaçladığı şeyi yapacaktır. Bunun aksine, açık bir yazım hatası olmadan floatifade atamak doublegenellikle bir hatadır. Örtük double->floatdönüştürmeye izin veren tek zaman, ideal olmayandan daha fazla aşırı yükün seçilmesine neden olacağı zamandır. Bunu önlemek için doğru yolu çift-float implcit yasak olmamalı, ancak dönüşüm izin vermemek için öznitelikleri ile aşırı etiketleme olması gerektiğini poz verirdi.
supercat

8

Sınıf bir referans türüdür. Sınıfın bir nesnesi oluşturulduğunda, nesnenin atandığı değişken yalnızca bu belleğe bir başvuru içerir. Nesne başvurusu yeni bir değişkene atandığında, yeni değişken orijinal nesneyi ifade eder. Bir değişken üzerinden yapılan değişiklikler diğer değişkene yansıtılır çünkü ikisi de aynı verilere karşılık gelir. Yapı bir değer türüdür. Bir yapı oluşturulduğunda, yapının atandığı değişken yapının gerçek verilerini tutar. Yapı yeni bir değişkene atandığında kopyalanır. Bu nedenle yeni değişken ve orijinal değişken aynı verilerin iki ayrı kopyasını içerir. Bir kopyada yapılan değişiklikler diğer kopyayı etkilemez. Genel olarak, sınıflar daha karmaşık davranışları veya bir sınıf nesnesi oluşturulduktan sonra değiştirilmesi amaçlanan verileri modellemek için kullanılır.

Sınıflar ve Yapılar (C # Programlama Kılavuzu)


Birkaç ilişkili-bağımsız bağımsız değişkenin koli bandıyla (örneğin bir noktanın koordinatları) sabitlenmesinin gerekli olduğu durumlarda yapılar da çok iyidir. MSDN yönergeleri, nesneler gibi davranan, ancak kümeler tasarlarken çok daha az uygun yapılar üretmeye çalışıyorsa mantıklıdır; bazıları ikinci durumda neredeyse tamamen yanlıştır . Örneğin, bir tiple kapsüllenen değişkenlerin bağımsızlık derecesi ne kadar büyük olursa, değişmez bir sınıftan ziyade açık alan yapısı kullanmanın avantajı o kadar büyük olur.
supercat

6

MİT # 1: YAPI HAFİF SINIFLAR

Bu efsane çeşitli şekillerde gelir. Bazı insanlar, değer türlerinin yöntemlere veya başka önemli davranışlara sahip olamayacağına ya da sahip olamayacağına inanmaktadır; bunlar yalnızca ortak alanlar ya da basit özelliklerle basit veri aktarım türleri olarak kullanılmalıdır. DateTime türü buna iyi bir örnektir: sayı veya karakter gibi temel bir birim olması açısından bir değer türü olması mantıklıdır ve ayrıca aşağıdakilere dayalı hesaplamalar yapabilmesi de anlamlıdır. Değeri. Diğer yönden olan şeylere baktığımızda, veri aktarım türleri genellikle referans türleri olmalıdır - karar, türün basitliğine değil, istenen değere veya referans türü anlamına dayalı olmalıdır. Diğer insanlar performans açısından değer türlerinin referans türlerden “daha ​​hafif” olduğuna inanmaktadır. Gerçek şu ki, bazı durumlarda değer türleri daha performanslıdır - kutulu olmadıkları sürece çöp toplama gerektirmezler, tip tanımlama yüküne sahip değildirler ve örneğin, kayıt silme gerektirmezler. Ancak başka şekillerde referans türleri daha performanslıdır — parametre geçişi, değişkenlere değer atama, değer döndürme ve benzer işlemler yalnızca 4 veya 8 baytın kopyalanmasını gerektirir (32 bit veya 64 bit CLR'yi çalıştırmanıza bağlı olarak) ) tüm verileri kopyalamak yerine. ArrayList'in bir şekilde “saf” bir değer türü olup olmadığını ve bir ArrayList ifadesini tüm verilerini kopyalamayı içeren bir yönteme geçirip geçirmediğini düşünün! Hemen hemen her durumda, performans bu tür bir kararla gerçekten belirlenmez. Darboğazlar neredeyse asla olacağını düşündüğünüz yerde değildir ve performansa dayalı bir tasarım kararı vermeden önce, farklı seçenekleri ölçmelisiniz. İki inancın birleşiminin de işe yaramadığını belirtmek gerekir. Bir türün kaç yöntemi olduğu (bir sınıf veya yapı olsun) önemli değildir; örnek başına alınan bellek etkilenmez. (Kodun kendisi için kullanılan bellek açısından bir maliyet vardır, ancak bu her örnek için değil, bir kez gerçekleşir.)

MİT # 2: HEAP'TA YAŞAYAN REFERANS ÇEŞİTLERİ; YERDE CANLI DEĞER TÜRLERİ

Bu genellikle tekrarlayan kişinin tembellikten kaynaklanır. İlk bölüm doğrudur; öbek üzerinde her zaman bir referans türü örneği oluşturulur. Sorunlara neden olan ikinci kısım. Daha önce de belirttiğim gibi, bir değişkenin değeri bildirildiği yerde yaşar, bu nedenle int türünde bir örnek değişkeni olan bir sınıfınız varsa, belirli bir nesne için bu değişkenin değeri her zaman nesne için verilerin geri kalanı olduğu yerde olacaktır. yığın üzerinde. Yığında yalnızca yerel değişkenler (yöntemlerde bildirilen değişkenler) ve yöntem parametreleri yaşar. C # 2 ve sonrasında, bölüm 5'teki anonim yöntemlere baktığımızda görebileceğiniz gibi, bazı yerel değişkenler bile yığın üzerinde gerçekte yaşamıyor. BU KAVRAMLAR ŞİMDİ İLGİLİ Mİ? Yönetilen kod yazıyorsanız, çalışma zamanının belleğin en iyi nasıl kullanıldığı konusunda endişelenmesine izin vermeniz mümkündür. Aslında, dil belirtimi neyin nerede yaşadığına dair hiçbir garanti vermez; gelecekteki bir çalışma zamanı, onunla kurtulabileceğini biliyorsa yığın üzerinde bazı nesneler oluşturabilir veya C # derleyicisi yığını neredeyse hiç kullanmayan kod oluşturabilir. Bir sonraki mit genellikle sadece bir terminoloji konusudur.

MİT 3: C # TARAFINDAN REFERANS İLE NESNELER GEÇTİ

Bu muhtemelen en yaygın yayılan efsanedir. Yine, bu iddiayı sıklıkla yapan insanlar (her zaman olmasa da) C #'ın gerçekte nasıl davrandığını bilirler, ancak “referansla geç” in gerçekten ne anlama geldiğini bilmezler. Ne yazık ki, bunun ne anlama geldiğini bilen insanlar için kafa karıştırıcı. Referansla geçişin resmi tanımı, l-değerleri ve benzer bilgisayar bilimi terminolojisini içeren nispeten karmaşıktır, ancak önemli olan, bir değişkeni referansla geçerseniz, aradığınız yöntemin arayanın değişkeninin değerini değiştirebilmesidir. parametre değerini değiştirerek. Şimdi, bir referans türü değişkeninin değerinin nesnenin kendisi değil referans olduğunu unutmayın. Bir parametrenin başvurduğu nesnenin içeriğini, parametrenin kendisi referansla iletilmeden değiştirebilirsiniz. Örneğin,

void AppendHello(StringBuilder builder)
{
    builder.Append("hello");
}

Bu yöntem çağrıldığında, parametre değeri (bir StringBuilder referansı) değere göre iletilir. Yöntem içindeki oluşturucu değişkeninin değerini değiştirirseniz (örneğin, builder = null ifadesiyle); mitin aksine arayan tarafından bu değişiklik görülmezdi. Efsanenin sadece “referansla” bitinin değil, “nesnelerin geçtiği” bitin de not edilmesi ilginçtir. Nesnelerin kendileri hiçbir zaman referans veya değere göre geçirilmez. Bir başvuru türü söz konusu olduğunda, değişken başvuru ile iletilir veya bağımsız değişkenin değeri (başvuru) değere göre iletilir. Başka bir şey dışında, null değeri bir değer argümanı olarak kullanıldığında ne olacağı sorusunu cevaplar - nesneler aktarılırsa, geçecek bir nesne olmayacağı için sorunlara neden olur! Yerine, null referans, diğer referanslarla aynı şekilde değere göre iletilir. Bu hızlı açıklama sizi şaşkına çevirdiyse, “C # 'da geçen parametre” makaleme bakmak isteyebilirsiniz (http://mng.bz/otVt ). Bu efsaneler sadece etrafta değil. Boks ve kutudan çıkarma, yanlış anlaşılmaları için adil payları için geliyor, ki bu da bir sonraki adımda temizlemeye çalışacağım.

Referans: Derinlik 3. Baskıda C #, Jon Skeet


1
Doğru olduğunu varsayarak çok iyi. Referans eklemek için de çok iyi.
NoChance

5

Bence iyi bir ilk yaklaşım "asla" değildir.

Bence iyi bir ikinci yaklaşım "asla" değildir.

Eğer mükemmellik için çaresizseniz, bunları düşünün, ama sonra daima ölçün.


24
Bu cevaba katılmıyorum. Yapılar birçok senaryoda meşru bir kullanıma sahiptir. İşte bir örnek - veri aktarım süreçleri atomik bir şekilde.
Franci Penov

25
Yayınınızı düzenlemeli ve puanlarınızla ilgili ayrıntılı bilgi vermelisiniz - fikrinizi verdiniz, ancak bu görüşü neden aldığınızla desteklemelisiniz.
Erik Forbes

4
Yapıları kullanmak için Totin 'Chip kartına ( en.wikipedia.org/wiki/Totin%27_Chip ) eşdeğer bir ihtiyaç duyduklarını düşünüyorum . Ciddi anlamda.
Greg

4
87,5 bin kişilik bir kişi nasıl böyle bir cevap gönderir? Çocukken mi yaptı?
Rohit Vipin Mathews

3
@Rohit - altı yıl önceydi; site standartları o zaman çok farklıydı. bu hala kötü bir cevap, ama haklısın.
Andrew Arnold

5

Ben sadece Windows Communication Foundation [WCF] Namlu Boru ile uğraşıyordum ve veri alışverişinin referans türü yerine değer türünde olmasını sağlamak için Yapılar kullanmanın mantıklı olduğunu fark ettim .


1
Bu en iyi ipucu, IMHO.
Ivan

4

C # yapısı bir sınıfa hafif bir alternatiftir. Sınıfla hemen hemen aynı şeyi yapabilir, ancak sınıf yerine bir yapı kullanmak daha az "pahalı" olur. Bunun nedeni biraz tekniktir, ancak özetlemek gerekirse, yeni örneklenmiş yapıların yığına yerleştirildiği yığına, bir sınıfın yeni örnekleri yerleştirilir. Ayrıca, sınıflarda olduğu gibi yapılara yapılan başvurularla uğraşmıyorsunuz, bunun yerine doğrudan struct örneği ile çalışıyorsunuz. Bu aynı zamanda bir yapıya bir işleve geçtiğinizde, başvuru yerine değere göre olduğu anlamına gelir. Bu konuda fonksiyon parametreleri hakkında daha fazlası var.

Bu nedenle, daha basit veri yapılarını temsil etmek istediğinizde ve özellikle de çok sayıda örnek oluşturacağınızı biliyorsanız, yapıları kullanmalısınız. .NET çerçevesinde Microsoft'un sınıflar yerine yapıları kullandığı birçok örnek vardır, örneğin Nokta, Dikdörtgen ve Renk yapısı.



3

Yapı veya değer türleri aşağıdaki senaryolarda kullanılabilir -

  1. Nesnenin çöp toplama ile toplanmasını önlemek istiyorsanız.
  2. Basit bir türse ve hiçbir üye işlevi örnek alanlarını değiştirmezse
  3. Başka türlerden türetmeye veya başka türlere türetmeye gerek yoksa.

Bu bağlantıda değer türleri ve değer türleri hakkında daha fazla bilgi edinebilirsiniz.


3

Kısaca, aşağıdaki durumlarda struct kullanın:

1- Nesne özelliklerinizin / alanlarınızın değiştirilmesi gerekmez. Yani onlara sadece bir başlangıç ​​değeri vermek ve sonra okumak istiyorsunuz.

2- nesnenizdeki özellikler ve alanlar değer tipindedir ve çok büyük değildir.

Bu durumda, hem yığınlar hem de yığınlar yerine yalnızca yığınlar kullandığından (sınıflarda) daha iyi performans ve optimize edilmiş bellek ayırma için yapılardan yararlanabilirsiniz.


2

Şeyler için nadiren bir yapı kullanıyorum. Ama bu sadece benim. Nesnenin boş değerli olup olmamasına bağlı.

Diğer cevaplarda belirtildiği gibi, sınıfları gerçek dünyadaki nesneler için kullanıyorum. Ayrıca küçük miktarlarda veri depolamak için kullanılan yapıların zihniyetine sahibim.


-11

Yapılar çoğu açıdan sınıflar / nesneler gibidir. Yapı fonksiyonlar, üyeler içerebilir ve kalıtsal olabilir. Ancak yapılar sadece veri tutmak için C # 'da kullanılır . Yapılar sınıflardan daha az RAM alır ve çöp toplayıcının toplaması daha kolaydır . Ancak yapınızda işlevleri kullandığınızda, derleyici aslında bu yapıyı sınıf / nesne ile çok benzer şekilde alır, bu nedenle işlevlerle bir şey istiyorsanız , sınıf / nesne kullanın .


2
Yapılar, bkz kalıtsal ALINMAYACAK msdn.microsoft.com/en-us/library/0taef578.aspx
HimBromBeere
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.