.NET'teki yapıların kalıtımı desteklemediğini biliyorum, ancak neden bu şekilde sınırlı oldukları tam olarak açık değil .
Yapıların diğer yapılardan devralmasını engelleyen teknik neden nedir?
.NET'teki yapıların kalıtımı desteklemediğini biliyorum, ancak neden bu şekilde sınırlı oldukları tam olarak açık değil .
Yapıların diğer yapılardan devralmasını engelleyen teknik neden nedir?
Yanıtlar:
Değer türlerinin kalıtımı destekleyememesinin nedeni dizilerdir.
Sorun, performans ve GC nedenleriyle, değer türü dizilerinin "satır içi" olarak depolanmasıdır. Örneğin, belirli bir new FooType[10] {...}
halinde, FooType
bir referans türüdür 11 nesneler yönetilen yığın (her birine, örneğin dizi için bir, ve 10) üzerinde oluşturulan,. Eğer FooType
bunun yerine bir değer türü, sadece bir örneği Yönetilen öbekte oluşturulacak - dizinin kendisi (her dizi değeri olarak dizi ile "satır içi" saklanacaktır).
Şimdi, değer türleriyle kalıtımımız olduğunu varsayalım. Dizilerin yukarıdaki "satır içi depolama" davranışıyla birleştirildiğinde , C ++ ' da görülebileceği gibi Kötü Şeyler meydana gelir .
Bu sözde C # kodunu düşünün:
struct Base
{
public int A;
}
struct Derived : Base
{
public int B;
}
void Square(Base[] values)
{
for (int i = 0; i < values.Length; ++i)
values [i].A *= 2;
}
Derived[] v = new Derived[2];
Square (v);
Normal dönüştürme kurallarına göre, a Derived[]
, a'ya dönüştürülebilir Base[]
(daha iyi veya daha kötü), bu nedenle yukarıdaki örnek için s / struct / class / g yaparsanız, beklendiği gibi sorunsuz bir şekilde derlenir ve çalışır. Ama eğer Base
ve Derived
değer türleri vardır ve diziler o zaman bir sorun var, satır içi değerleri depolar.
Bir sorunumuz var çünkü Square()
hakkında hiçbir şey bilmiyoruz Derived
, dizinin her öğesine erişmek için yalnızca işaretçi aritmetiğini kullanacak ve sabit bir miktar ( sizeof(A)
) ile artacak . Meclis belli belirsiz şöyle olurdu:
for (int i = 0; i < values.Length; ++i)
{
A* value = (A*) (((char*) values) + i * sizeof(A));
value->A *= 2;
}
(Evet, bu iğrenç bir derleme, ancak önemli olan, türetilmiş bir türün kullanıldığına dair herhangi bir bilgi olmadan, bilinen derleme zamanı sabitlerinde dizi boyunca artış yapacağımızdır.)
Yani, bu gerçekten olsaydı, bellek bozulması sorunlarımız olurdu. Spesifik olarak, içinde Square()
, aslında değiştirici values[1].A*=2
olurdu !values[0].B
BU hata ayıklamayı deneyin !
Yapıların kalıtımı desteklediğini düşünün. Ardından şunu beyan edin:
BaseStruct a;
InheritedStruct b; //inherits from BaseStruct, added fields, etc.
a = b; //?? expand size during assignment?
yapı değişkenlerinin sabit boyuta sahip olmadığı anlamına gelir ve bu yüzden referans türlerimiz var.
Daha da iyisi, şunu düşünün:
BaseStruct[] baseArray = new BaseStruct[1000];
baseArray[500] = new InheritedStruct(); //?? morph/resize the array?
Foo
kaynaklanan devralma Bar
bir izin vermemelidir Foo
bir atanacak Bar
(1) türünde bir özel olarak adlandırılmış üyesini oluşturun: ama bir yapı ilan yolu yararlı etkilerin birkaç olanak verebilecek Bar
ilk öğe olarak Foo
ve sahip Foo
bulunmaktadır bu üyelere takma ad veren üye isimleri, eskiden a kullanmak için uyarlanmış Bar
olan koda , tüm referansları ile değiştirmek zorunda kalmadan izin verir ve bir grup olarak tüm alanlarını okuma ve yazma yeteneğini muhafaza eder ; ...Bar
Foo
thing.BarMember
thing.theBar.BarMember
Bar
Yapılar referanslar kullanmazlar (kutu içine alınmadıkları sürece, ancak bundan kaçınmaya çalışmalısınız), bu nedenle, bir referans işaretçisi aracılığıyla hiçbir indirme olmadığı için polimorfizm anlamlı değildir. Nesneler normalde öbek üzerinde yaşar ve referans işaretçileri aracılığıyla başvurulur, ancak yapılar yığın üzerinde tahsis edilir (kutuya alınmadıkları sürece) veya öbek üzerinde bir referans türü tarafından kullanılan belleğin "içinde" tahsis edilir.
Foo
yapı türünde bir alan vardır Bar
bakımdan mümkün Bar
böylece kendi gibi 'ın üyeleri Bir Point3d
sınıf, örneğin a'yı kapsülleyebilir Point2d xy
ancak X
bu alana ya xy.X
da olarak atıfta bulunabilir X
.
İşte dokümanlar şöyle diyor:
Yapılar, özellikle değer semantiği olan küçük veri yapıları için kullanışlıdır. Karmaşık sayılar, bir koordinat sistemindeki noktalar veya bir sözlükteki anahtar-değer çiftlerinin tümü, yapıların iyi örnekleridir. Bu veri yapılarının anahtarı, az sayıda veri üyesine sahip olmaları, kalıtım veya referans kimlik kullanımını gerektirmemeleri ve atamanın referans yerine değeri kopyaladığı değer semantiği kullanılarak uygun şekilde uygulanabilmeleridir.
Temel olarak, basit verileri tutmaları gerekir ve bu nedenle kalıtım gibi "ekstra özelliklere" sahip değildirler. Muhtemelen sınırlı bir tür kalıtımı desteklemeleri teknik olarak mümkün olacaktır (yığında olmaları nedeniyle polimorfizm değil), ancak kalıtımı desteklememenin de bir tasarım seçeneği olduğuna inanıyorum (.NET'teki diğer birçok şey gibi) diller.)
Öte yandan, mirasın faydalarına katılıyorum ve sanırım hepimiz bir struct
başkasından miras almamızı istediğimiz noktaya geldik ve bunun mümkün olmadığını anladık . Ancak bu noktada, veri yapısı muhtemelen o kadar gelişmiştir ki zaten bir sınıf olmalıdır.
Point3D
bir gelen Point2D
, siz mümkün olmaz a Point3D
yerine a kullanmak için Point2D
, ama Point3D
tamamen sıfırdan yeniden uygulamak zorunda kalmazsınız .) Ben de böyle yorumladım ...
class
üzerinde struct
uygun olduğunda.
Yapı doğrudan yığının üzerine yerleştirildiği için sınıf benzeri kalıtım mümkün değildir. Miras alan bir yapı, ebeveynden daha büyük olacaktır, ancak JIT bunu bilmiyor ve çok daha az alana çok fazla şey koymaya çalışıyor. Biraz belirsiz geliyor, bir örnek yazalım:
struct A {
int property;
} // sizeof A == sizeof int
struct B : A {
int childproperty;
} // sizeof B == sizeof int * 2
Bu mümkün olursa, aşağıdaki kod parçacığında çökebilir:
void DoSomething(A arg){};
...
B b;
DoSomething(b);
Alan, B boyutu için değil, A boyutu için ayrılmıştır.
Düzeltmek istediğim bir nokta var. Yapıların miras alınamamasının nedeni yığın üzerinde yaşadıkları için doğru olanı olsa da, aynı zamanda yarım doğru açıklamadır. Başka bir değer türü gibi yapılar, olabilir istif içinde yaşamaktadır. Değişkenin nerede açıklandığına bağlı olacağından, bunlar ya yığın içinde ya da yığın içinde yaşayacaklardır . Bu, sırasıyla yerel değişkenler veya örnek alanları olduklarında olacaktır.
Bunu söylerken, Cecil Has a Name'i doğru bir şekilde çiviledi.
Ben değer türleri, bu vurgulamak isteriz olabilir yığın yaşıyor. Bu, her zaman böyle yaptıkları anlamına gelmez. Yöntem parametreleri dahil yerel değişkenler olacaktır. Diğerleri olmayacak. Yine de, miras alınamamalarının nedeni hala budur. :-)
Yapılar yığın üzerinde tahsis edilir. Bu, değer semantiğinin hemen hemen ücretsiz olduğu ve yapı üyelerine erişmenin çok ucuz olduğu anlamına gelir. Bu, polimorfizmi engellemez.
Her yapının sanal işlev tablosuna bir işaretçi ile başlamasını sağlayabilirsiniz. Bu bir performans sorunu olabilir (her yapı en azından bir işaretçi boyutunda olacaktır), ancak yapılabilir. Bu, sanal işlevlere izin verir.
Alan eklemeye ne dersiniz?
Peki, yığına bir yapı ayırdığınızda, belirli bir alan ayırırsınız. Gerekli alan derleme zamanında belirlenir (ister vaktinden önce ister JITting sırasında). Alan ekler ve ardından bir temel türe atarsanız:
struct A
{
public int Integer1;
}
struct B : A
{
public int Integer2;
}
A a = new B();
Bu, yığının bilinmeyen bazı bölümlerinin üzerine yazacaktır.
Alternatif, çalışma zamanının bunu, herhangi bir A değişkenine yalnızca sizeof (A) bayt yazarak engellemesidir.
B, A'daki bir yöntemi geçersiz kılarsa ve Tamsayı2 alanına başvurursa ne olur? Çalışma zamanı bir MemberAccessException oluşturur veya yöntem bunun yerine yığındaki bazı rasgele verilere erişir. Bunların hiçbirine izin verilmez.
Yapıları polimorfik olarak kullanmadığınız veya devralırken alan eklemediğiniz sürece, yapı kalıtımına sahip olmak tamamen güvenlidir. Ancak bunlar çok da kullanışlı değil.
Bu çok sık sorulan bir soru gibi görünüyor. Değer türlerinin değişkeni tanımladığınız yerde "yerinde" saklandığını eklemek istiyorum; ayrı uygulama ayrıntıları gelen, olduğu bu araçlar hiçbir nesne hakkında bir şeyler söylüyor nesne başlığı, sadece değişken tür verilerin orada ikamet bilir.
Yapılar arayüzleri destekler, bu yüzden bu şekilde bazı polimorfik şeyler yapabilirsiniz.
IL, yığın tabanlı bir dildir, bu nedenle bir yöntemi bir argümanla çağırmak şuna benzer:
Yöntem çalıştığında, argümanını almak için yığından bazı baytlar çıkarır. Bağımsız değişken ya bir başvuru türü işaretçisi (32 bit üzerinde her zaman 4 bayt) ya da boyutun her zaman tam olarak bilindiği bir değer türü olduğu için tam olarak kaç baytın açılacağını bilir .
Bu bir başvuru türü işaretçisi ise, yöntem öbekte nesneyi arar ve tam olarak bu tür için belirli yöntemi işleyen bir yöntem tablosuna işaret eden tür tutamacını alır. Değer türü ise, değer türleri mirası desteklemediğinden, yöntem tablosuna bakmaya gerek yoktur, bu nedenle yalnızca bir olası yöntem / tür kombinasyonu vardır.
Değer türleri kalıtımı destekliyorsa, o zaman yapının belirli türünün değerinin yanı sıra yığına yerleştirilmesi gerektiğinden fazladan ek yük olacaktır, bu da türün belirli somut örneği için bir tür yöntem tablosu araması anlamına gelecektir. Bu, değer türlerinin hız ve verimlilik avantajlarını ortadan kaldıracaktır.