Sınıf yerine yapıyı ne zaman kullanırsınız? [kapalı]


174

Yapıları ve sınıfları ne zaman kullanacağınızla ilgili kurallarınız nelerdir? Bu terimlerin C # tanımını düşünüyorum ama eğer diliniz benzer kavramlara sahipse fikrinizi de duymak isterim.

Sınıfları hemen hemen her şey için kullanma eğilimindeyim ve yalnızca çok basit bir şey varsa ve PhoneNumber gibi bir değer türü olması durumunda yapıları kullanmaya meyilliyim. Ancak bu nispeten küçük bir kullanım gibi görünüyor ve umarım daha ilginç kullanım örnekleri vardır.



7
@Frustrated Kesinlikle ilgili olmasına rağmen, bir soruyu başka bir sitenin kopyası olarak işaretleyemiyorum.
Adam Lear

@ Anna Lear: Biliyorum, kopya olarak yakınlaşmamak için göç etmek için oy kullandım. Bir kez taşındığında, düzgün bir şekilde yinelenen olarak kapatılabilir .
FrustratedWithFormsDesigner

5
@Frustrated Taşınması gerektiğini sanmıyorum.
Adam Lear

15
Bu, P.SE'ye ve SO'ya daha uygun bir soru gibi görünüyor. Bu yüzden burada sordum.
RationalGeek

Yanıtlar:


169

İzlenecek genel kural, yapıların bir zamanlar yaratılamayan, küçük, basit (tek seviyeli) ilgili özellik koleksiyonları olması gerektiğidir; başka bir şey için bir sınıf kullanın.

C #, yapıların ve sınıfların, tanımlayıcı anahtar kelimeden başka bildirimde açık bir farklılığı olmaması nedeniyle iyidir; bu nedenle, bir yapıyı bir sınıfa "yükseltmeniz" veya bir sınıfı bir yapıya dönüştürmenin "gerektiğini" düşünüyorsanız, bu anahtar kelimeyi değiştirmenin basit bir meselesidir (başka bir kaç tane var; yapılar elde edilemez) başka herhangi bir sınıf veya yapı türünden ve açıkça varsayılan bir parametresiz yapıcı tanımlayamazlar).

"Çoğunlukla" diyorum, çünkü yapılar hakkında bilinmesi gereken en önemli şey, onlar değer türleri oldukları için, onlara sınıflar gibi davranmaları (referans türleri) bir buçuk acı verebilir. Özellikle, bir yapının özelliklerini değiştirilebilir hale getirmek, beklenmeyen davranışlara neden olabilir.

Örneğin, iki özelliğe sahip bir SimpleClass sınıfınız olduğunu varsayalım, A ve B. Bu sınıfın bir kopyasını başlatıyorsunuz, A ve B'yi başlatıyorsunuz ve örneği başka bir yönteme aktarıyorsunuz. Bu yöntem, A ve B'yi daha da değiştirir. Çağıran işlevde (örneği oluşturan yöntem), örneğinizin A ve B'si, çağrılan yöntem tarafından kendilerine verilen değerlere sahip olacaktır.

Şimdi, onu bir yapı haline getirdin. Özellikler hala değişkendir. Aynı işlemleri daha önce olduğu gibi aynı sözdizimiyle gerçekleştiriyorsunuz, ancak şimdi A ve B'nin yeni değerleri, yöntemi çağırdıktan sonra görünmüyor. Ne oldu? Sınıfınız şimdi bir yapı, yani bir değer türü. Bir yönteme değer türü iletirseniz, varsayılan (bir çıkış veya ref anahtar sözcüğü olmadan) "değere göre" geçirmektir; Örneğin sığ bir kopyası yöntem tarafından kullanılmak üzere yaratılır ve daha sonra yöntem ilk örneği bozulmadan bırakıldığında yok edilir.

Yapınızın bir üyesi olarak bir referans türüne sahip olsaydınız, bu daha da kafa karıştırıcı hale gelirdi ( neredeyse tüm durumlarda , izin verilmez, ancak son derece kötü bir uygulama); sınıf klonlanmayacak (yalnızca yapıya referansta bulunacak), bu nedenle yapıya yapılan değişiklikler orijinal nesneyi etkilemeyecek, ancak yapıya ait alt sınıftaki değişiklikler WILL'i arama kodundan etkileyecektir. Bu, değişken yapıları çok tutarsız durumlara koyabilir, bu da gerçek sorunun olduğu yerden çok uzun bir süre önce hatalara neden olabilir.

Bu nedenle, C # üzerindeki hemen hemen her otorite yapılarınızı her zaman değiştirilemez kıldığını söyler; tüketicinin mülklerin değerlerini yalnızca bir nesnenin yapımında belirtmesine izin vermek ve bu vakanın değerlerini değiştirmek için hiçbir zaman hiçbir yol belirtmemek. Salt okunur alanlar veya yalnızca alma özellikleri kuraldır. Tüketici değeri değiştirmek isterse, eskisinin değerlerine dayanarak yeni bir nesne yaratabilir, istedikleri değişikliklerle veya aynı şeyi yapacak bir yöntem çağırabilir. Bu, onları yapınızın tek bir örneğine, bölünemez ve diğerlerinden farklı (ancak muhtemelen eşdeğer) tek bir kavramsal "değer" olarak muamele etmeye zorlar. Tipinize göre depolanan bir "değer" üzerinde işlem yaparlarsa, başlangıç ​​değerlerinden farklı yeni bir "değer" alırlar,

İyi bir örnek için DateTime türüne bakın. Bir DateTime örneğinin alanlarından herhangi birini doğrudan atayamazsınız; ya yeni bir tane oluşturmanız ya da mevcut olanın üzerine yeni bir örnek üretecek bir yöntem çağırmanız gerekir. Bunun nedeni, tarih ve saatin, 5 sayısı gibi bir "değer" olmasıdır ve 5 sayısındaki değişiklik, 5 olmayan yeni bir değere yol açar. çünkü ona 1 tane ekledin. DateTimes aynı şekilde çalışır; 12:00, bir dakika eklerseniz, 12:01 "haline gelmez" yerine 12:01 den farklı bir değer elde edersiniz. Bu, türünüze ilişkin mantıksal bir durum ise (.NET'te yerleşik olmayan iyi kavramsal örnekler Para, Mesafe, Ağırlık ve işlemlerin değerin tüm bölümlerini göz önünde bulundurması gereken bir UOM diğer miktarlarıdır), daha sonra bir yapı kullanın ve buna göre tasarlayın. Bir nesnenin alt öğelerinin bağımsız olarak değişken olması gereken diğer birçok durumda, bir sınıf kullanın.


1
İyi cevap! Birini 'Domain Driven Development' metodolojisine ve kitabına okurken, birisinin neden uygulamaya çalıştığınıza bağlı olarak neden sınıfları veya yapıları kullanması gerektiği konusundaki fikrinizle ilgili olduğunu düşünüyorum.
Maxim Popravko

1
Bahsetmeye değer sadece küçük bir detay: kendi varsayılan kurucunuzu bir yapı üzerinde tanımlayamazsınız. Her şeyi varsayılan değerine başlatanla yapmak zorundasınız. Genellikle bu oldukça küçüktür, özellikle de yapı olarak iyi aday olan sınıfta. Ancak, bir sınıfı bir yapıya değiştirmenin bir anahtar kelimeyi değiştirmekten daha fazlasını ifade edebileceği anlamına gelecektir.
Laurent Bourgault-Roy

1
@ LaurentBourgault-Roy - Doğru. Başka gotchalar da var; örneğin yapılar miras hiyerarşisine sahip olamaz. Arabirimleri uygulayabilirler, ancak System.ValueType dışındaki herhangi bir şeyden türetemezler (örtük olarak yapılarıyla).
KeithS

Bir JavaScript dev olarak iki şey sormam gerekiyor. Tipik yapı kullanımlarından farklı nesne hazırlıkları nasıldır ve yapıların değişmez olması neden önemlidir?
Erik,

1
@ErikReppen: Her iki sorunuza cevaben: Bir yapı bir işleve geçirildiğinde, değere göre iletilir. Bir sınıfla, sınıfa yapılan bir referansı geçersiniz (hala değere göre). Eric Lippert bunun neden bir sorun olduğunu tartışıyor: blogs.msdn.com/b/ericlippert/archive/2008/05/14/… . KeithS'in son paragrafı da bu soruları kapsar.
Brian,

14

Cevap: "Saf veri yapıları için bir yapı ve işlemleri olan nesneler için bir sınıf kullanın" kesinlikle IMO’dur. Bir yapı çok sayıda özelliğe sahipse, bir sınıf neredeyse her zaman daha uygundur. Microsoft, sık sık, verimlilik açısından, türünüzün 16 bayttan büyük olması halinde bir sınıf olması gerektiğini söyler.

https://stackoverflow.com/questions/1082311/why-should-a-net-struct-be-less-than-16-bytes


1
Kesinlikle. Yapabilseydim çok oy kullanırdım. Bu yapının fikrinden nereden geldiğimi anlamıyorum ve nesneler değil. C # içindeki bir yapı sadece yığında tahsis etmek için kullanılan bir mekanizmadır. Tüm yapının kopyaları, sadece bir nesne göstergesinin bir kopyası yerine geçirilir.
mike30,

2
@mike - C # yapılarının yığında olması gerekmez.
Lee

1
@mike "Yapılar veri içindir" fikri muhtemelen uzun yıllar süren C programlamanın edindiği alışkanlıktan gelir. C'nin sınıfları yoktur ve yapıları yalnızca veri konteynerleridir.
Konamiman

Açıkçası Michael K'ın cevabını okuyan en az 3 C programcısı (ayağa kalktı) var ;-)
bytedev 16

10

A classile a arasındaki en önemli fark struct, aşağıdaki durumda olan şeydir:

  Şey şey1 = yeni Şey ();
  thing1.somePropertyOrField = 5;
  Şey şey2 = şey1;
  thing2.somePropertyOrField = 9;

Son ifadenin etkisi üzerindeki etkisi ne olmalı thing1.somePropertyOrField? Eğer Thingbir yapı ve somePropertyOrFieldaçıktaki kamu alanı, nesneleri thing1ve thing2birbirinden "müstakil" olacak, bu nedenle ikinci deyimi etkilemez thing1. Eğer Thingbir sınıfsa, o zaman thing1ve thing2birbirine eklenecek ve böylece ikinci ifade yazacaktır thing1.somePropertyOrField. Eski anlambilimin daha anlamlı olacağı durumlarda bir yapı kullanılmalı ve sonraki anlambilimin daha anlamlı olacağı durumlarda bir sınıf kullanılmalıdır.

Bazı insanlar, bir şeyi değişken kılma arzusunun sınıf olma lehine bir argüman olduğunu söylese de, bunun tam tersi olduğunu söyleyebilirim: eğer bazı verileri tutmak için var olan bir şey değişebilir olacaksa ve Örneklerin bir şeye eklenip eklenmediği belli değilse, örneklerin başka hiçbir şeye bağlı olmadığını açıkça belirtmek için şey bir yapı (muhtemelen maruz kalan alanlarla) olmalıdır.

Örneğin, ifadeleri göz önünde bulundurun:

  Kişi bazı Kişi = myPeople.GetPerson ("123-45-6789");
  somePerson.Name = "Mary Johnson"; // "Mary Smith" olmuştu

İkinci ifade saklanan bilgiyi değiştirecek myPeoplemi? Eğer Personaçıkta alan yapı olduğunu, böyle olmaz, ve onun varlık açıktaki alan yapı açık bir sonucu olacaktır olmayacak olması ; eğer Personbir yapı varsa ve biri güncellemek isterse myPeople, birinin açıkça böyle bir şey yapması gerekir myPeople.UpdatePerson("123-45-6789", somePerson). Eğer Personbir sınıftır, ancak yukarıdaki kod içeriğini güncelleme asla olmadığını belirlemek için çok daha zor olabilir MyPeoplebunu hep güncellemek veya bazen güncelleyin.

Yapıların "değişmez" olması gerektiği nosyonuyla ilgili olarak, genel olarak aynı fikirde değilim. “Değişmez” yapılar için geçerli kullanım durumları vardır (değişmezlerin bir kurucuda uygulandığı yerlerde), ancak herhangi bir değişikliğin herhangi bir kısmının basitçe ortaya koyacağından daha fazla hataya neden olmak için bütün bir yapının yeniden yazılması gerektiğini doğrudan alanları Örneğin, bir düşünün PhoneNumber, kimin alanları yapı diğerleri arasında içerir AreaCodeve Exchange, biri de vardır herhalde List<PhoneNumber>. Aşağıdakilerin etkisi oldukça açık olmalıdır:

  (int i = 0; i <myList.Count; i ++) için
  {
    PhoneNumber theNumber = myList [i];
    if (theNumber.AreaCode == "312")
    {
      string newExchange = "";
      if (new312to708Exchanges.TryGetValue (theNumber.Exchange), yeniExchange dışında)
      {
        theNumber.AreaCode = "708";
        theNumber.Exchange = newExchange;
        myList [i] = sayı;
      }
    }
  }

Yukarıdaki kod bilir ya herhangi alanlar hakkında umurunda içinde hiçbir şeyin unutmayın PhoneNumberdışındaki AreaCodeve Exchange. Eğer PhoneNumber"değişmez" olarak adlandırılan bir yapı olsaydı withXX, her bir alan için bir yöntem sağlaması gerekliydi, ya da belirtilen alana girilen değeri tutan yeni bir yapı örneği getirecekti ya da gerekli olacaktı. Yapıdaki her alan hakkında bilmek yukarıdaki kod gibi. Tam olarak çekici değil.

BTW, yapıların makul türlere atıfta bulunulabileceği makul en az iki durum vardır.

  1. Yapının semantiği, nesnenin özelliklerini tutmak için bir kısayol yerine, söz konusu nesnenin kimliğini sakladığını gösterir. Örneğin, bir 'KeyValuePair', belirli düğmelerin kimliklerini koruyacaktır, ancak bu düğmelerin konumları, vurgu durumları vb. Hakkında kalıcı bir bilgi tutması beklenmeyecektir.
  2. Yapı, o nesneye tek referansı tuttuğunu, başka kimsenin referans alamayacağını ve bu nesneye yapılabilecek herhangi bir mutasyonun herhangi bir yere kaydedilmeden önce yapıldığını biliyor.

Eski senaryoda, nesnenin KİMLİĞİ değişmez olacaktır; ikincisinde, iç içe geçmiş nesnenin sınıfı değişmezliği zorlayamayabilir, ancak referansı tutan yapı olacaktır.


7

Yapıları yalnızca bir yönteme ihtiyaç duyulduğunda kullanmaya meyilliyim, bu yüzden bir sınıf yapmak çok "ağır" görünüyor. Bu yüzden bazen yapılara hafif nesneler olarak bakarım. DİKKAT: Bu her zaman işe yaramaz ve her zaman yapılacak en iyi şey değildir, bu yüzden gerçekten duruma bağlıdır!

EKSTRA KAYNAKLARI

Daha resmi bir açıklama için, MSDN diyor ki: http://msdn.microsoft.com/en-us/library/ms229017.aspx Bu link yukarıda belirtilenleri doğrular, yapılar sadece az miktarda veri depolamak için kullanılmalıdır.


5
Başka bir sitedeki sorular (bu site Yığın Taşması olsa bile ) yinelenen sayılmaz. Lütfen cevabınızı sadece başka yerlere bağlantılar değil gerçek bir cevap eklemek için genişletin. Bağlantılar değişirse, şimdi yazıldığı gibi cevabınız faydasız olacaktır ve bilgileri korumak ve Programcıları olabildiğince kendi kendine yeten hale getirmek istiyoruz. Teşekkürler!
Adam Lear

2
@ Anna Lear Bana haber verdiğin için teşekkürler, bundan sonra akılda kalacak!
rrazd

2
-1 "Yapıları yalnızca bir yönteme ihtiyaç duyulduğunda kullanmaya meyilliyim, bu yüzden bir sınıf yapmak çok" ağır "görünüyor. ... ağır mı? bu gerçekten ne anlama geliyor? ... ve gerçekten diyorsunuz ki, çünkü bir yöntem var, o zaman muhtemelen bir yapı olmalı?
bytedev

2

Saf veri yapıları için bir yapı ve işlemleri olan nesneler için bir sınıf kullanın.

Veri yapınız erişim kontrolü gerektirmiyorsa ve get / set dışında özel bir işlem yapmıyorsa, struct kullanın. Bu, tüm bu yapının veri için bir konteyner olduğunu açıkça ortaya koymaktadır.

Yapınız, yapıyı daha karmaşık bir şekilde değiştiren işlemlere sahipse, bir sınıf kullanın. Bir sınıf ayrıca, diğer sınıflarla argümanlar yoluyla yöntemlere etkileşime girebilir.

Bunu tuğla ile uçak arasındaki fark olarak düşünün. Bir tuğla bir yapıdır; uzunluk, genişlik ve yüksekliğe sahiptir. Fazla bir şey yapamaz. Bir uçağın kontrol yüzeylerini hareket ettirmek ve motor itişini değiştirmek gibi bir şekilde onu değiştiren birden fazla işlemi olabilir. Bir sınıf olarak daha iyi olurdu.


2
C # 'da "saf veri yapıları için bir yapı ve işlemleri olan nesneler için bir sınıf kullanın" bu saçmalık. Aşağıdaki cevaba bakınız.
bytedev

1
"Saf veri yapıları için bir yapı ve işlemleri olan nesneler için bir sınıf kullanın." Bu C / C ++ için geçerlidir, C # için değil
taktak004
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.