Yeniden yapılanma nedir?


163

Java'nın parametrik polimorfizmi (Generics) silme ile uyguladığını biliyorum. Silme işleminin ne olduğunu anlıyorum.

C # 'ın parametrik polimorfizmi yeniden birleşme ile uyguladığını biliyorum. Bunun yazmana neden olduğunu biliyorum

public void dosomething(List<String> input) {}
public void dosomething(List<Int> input) {}

veya bazı Parametrelenmiş türünün tür parametresi ne zamanında bilebilir, ama anlamak kalmamasıdır olduğunu .

  • Birleştirilmiş tip nedir?
  • Birleştirilmiş değer nedir?
  • Bir tür / değer yeniden birleştirildiğinde ne olur?

Bu bir cevap değil, ama Bir şekilde yardımcı olabilir: beust.com/weblog/2011/07/29/erasure-vs-reification
Heringer

@heringer, "silme nedir" sorusuna oldukça iyi yanıt veriyor gibi görünüyor ve temelde "neyi değiştirmeme" yi "silmeme" ile cevaplıyor gibi görünüyor - burada göndermeden önce ilk olarak bir cevap ararken bulduğum ortak bir tema.
Martijn

5
... ve beni yeniden düşünmeye vardı ification bir dönüştürme işlemidir switchbir karşı yapı arkasını if/ else, daha önce bir dönüştürülen edilmişti zaman if/ ' elsea kadar switch...
Dijital Travma

8
Res , reis Latince şey bu yüzden, Şeyleşme anlamıyla thingification . C # 'ın bu terimi kullanmasına katkıda bulunacak faydalı bir şeyim yok, ama kendi başına kullandıkları gerçeği beni gülümsetiyor.
KRyan

Yanıtlar:


209

Yeniden birleşme soyut bir şey alma ve somut bir şey yaratma sürecidir.

Terimi Şeyleşme C # jenerik bir yol açan süreç anlamına gelir genel tür tanımı ve bir veya daha fazla genel tür bağımsız değişkenleri (özet bir şey) yeni oluşturmak için birleştirilir genel tür (beton bir şey).

İfade için bu farklı, bu tanımını alma işlemidir List<T>ve intbir beton üretim List<int>türü.

Bunu daha iyi anlamak için aşağıdaki yaklaşımları karşılaştırın:

  • Java jeneriklerinde, genel tür tanımı, izin verilen tüm tür bağımsız değişken kombinasyonlarında paylaşılan esasen bir somut genel türe dönüştürülür. Bu nedenle, birden çok (kaynak kodu seviyesi) tür bir (ikili seviye) türüyle eşleştirilir - ancak bunun sonucunda bir örneğin tür bağımsız değişkenleri hakkındaki bilgiler bu örnekte (tür silme) atılır .

    1. Bu uygulama tekniğinin bir yan etkisi olarak, yerel olarak izin verilen tek genel tür argümanları, somut türlerinin ikili kodunu paylaşabilen türlerdir; depolama yerleri değiştirilebilir temsillere sahip olanlar anlamına gelir; referans türleri anlamına gelir. Değer türlerini genel tür bağımsız değişkenleri olarak kullanmak, bunları boks gerektirir (bunları basit bir başvuru türü paketleyicisine yerleştirir).
    2. Jenerikleri bu şekilde uygulamak için hiçbir kod kopyalanmaz.
    3. Çalışma zamanında bulunabilecek tür bilgileri (yansıma kullanılarak) kaybolur. Bu da, genel bir türün uzmanlaşmasının ( belirli bir genel argüman kombinasyonu için özel kaynak kodu kullanma yeteneğinin ) çok sınırlı olduğu anlamına gelir.
    4. Bu mekanizma, çalışma zamanı ortamından destek gerektirmez.
    5. Java programının veya JVM tabanlı bir dilin kullanabileceği tür bilgilerini saklamak için birkaç geçici çözüm vardır .
  • C # jeneriklerinde, genel tip tanımı çalışma zamanında bellekte tutulur. Yeni bir beton türü gerektiğinde, çalışma zamanı ortamı genel tür tanımını ve tür bağımsız değişkenlerini birleştirir ve yeni türü (yeniden birleştirme) oluşturur. Bu yüzden çalışma zamanında tür argümanlarının her birleşimi için yeni bir tür elde ederiz .

    1. Bu uygulama tekniği, herhangi bir tür argüman kombinasyonunun başlatılmasına izin verir. Değer türlerini genel tür bağımsız değişkenleri olarak kullanmak boksa neden olmaz, çünkü bu türler kendi uygulamalarını alır. ( Boks elbette C # 'da hala var - ama diğer senaryolarda oluyor, bu değil.)
    2. Kod çoğaltma bir sorun olabilir - ancak pratikte böyle değildir, çünkü yeterince akıllı uygulamalar ( Microsoft .NET ve Mono içerir ) bazı örneklemeler için kod paylaşabilir.
    3. Yansıma kullanarak tür argümanlarını inceleyerek bir dereceye kadar uzmanlaşmayı sağlayan tür bilgisi korunur. Bununla birlikte, uzmanlaşma derecesi sınırlıdır, çünkü herhangi bir yeniden birleşme gerçekleşmeden önce genel bir tür tanımının derlenmesi (bu, tanımın tür parametreleri üzerindeki kısıtlamalara karşı derlenmesiyle yapılır - bu nedenle derleyici, Belirli tür argümanları olmasa bile tanımı "anlayın" .
    4. Bu uygulama tekniği büyük ölçüde çalışma zamanı desteğine ve JIT derlemesine bağlıdır (bu nedenle C # jeneriklerinin dinamik kod oluşturmanın kısıtlandığı iOS gibi platformlarda bazı sınırlamaları olduğunu sık sık duyarsınız ).
    5. C # jenerikleri bağlamında, yeniden çalışma ortamı tarafından sizin için yeniden yapılanma yapılır. Bununla birlikte, genel bir tür tanımı ve somut bir genel tür arasındaki farkı daha sezgisel olarak anlamak istiyorsanız, System.Typesınıfı kullanarak her zaman kendi başınıza bir ilişki gerçekleştirebilirsiniz (somutlaştırdığınız belirli genel tür argüman kombinasyonu yapmamış olsa bile ' t doğrudan kaynak kodunuzda görünür).
  • C ++ şablonlarında, şablon tanımı derleme zamanında bellekte tutulur. Kaynak kodda bir şablon türünün her yeni bir örneği gerektiğinde, derleyici şablon tanımını ve şablon bağımsız değişkenlerini birleştirir ve yeni türü oluşturur. Böylece, şablon bağımsız değişkenlerinin her birleşimi için derleme zamanında benzersiz bir tür elde ederiz .

    1. Bu uygulama tekniği, herhangi bir tür argüman kombinasyonunun başlatılmasına izin verir.
    2. Bunun ikili kodu çoğalttığı bilinmektedir, ancak yeterince akıllı bir araç zinciri bunu algılayabilir ve bazı örneklemeler için kodu paylaşabilir.
    3. Şablon tanımının kendisi "derlenmez" - yalnızca somut örnekleri derlenir . Bu, derleyiciye daha az kısıtlama getirir ve daha fazla şablon uzmanlığı sağlar .
    4. Şablon örnekleri derleme zamanında gerçekleştirildiğinden, burada da çalışma zamanı desteğine gerek yoktur.
    5. Bu süreç son zamanlarda özellikle Rust topluluğunda monomorfizasyon olarak adlandırılmaktadır . Kelime, jeneriklerin geldiği kavramın adı olan parametrik polimorfizmin aksine kullanılır .

7
C ++ şablonları ile büyük karşılaştırma ... C # ve Java'nın jenerikleri arasında bir yere düşüyor gibi görünüyor. C # gibi farklı belirli genel türleri işlemek için farklı kod ve yapılarınız var, ancak hepsi Java'daki gibi derleme zamanında yapılır.
Luaan

3
Ayrıca, C ++ 'da bu, her (veya sadece bazı) somut tiplerin farklı uygulamalara sahip olabileceği şablon uzmanlığı sunmayı sağlar. Açıkçası Java'da mümkün değil, ama C # 'da.
quetzalcoatl

@quetzalcoatl kullanmak için bir nedeni işaretçi türleri ile üretilen kod miktarını azaltmak ve C # sahne arkasındaki referans türleri ile karşılaştırılabilir bir şey yapar. Yine de, bunu kullanmanın sadece bir nedeni var ve şablon uzmanlığının güzel olacağı kesinlikle zamanlar var.
Jon Hanna

Java için, tür bilgisi silinirken, derleyici tarafından atımların eklendiğini ve bayt kodunun jenerik öncesi bayt kodundan ayırt edilemez hale geldiğini eklemek isteyebilirsiniz.
Rusty Core

27

Yeniden birleşme genellikle (bilgisayar biliminin dışında) "bir şeyi gerçek yapmak" anlamına gelir.

Programlamada, dilin kendisiyle ilgili bilgilere erişebiliyorsak , bir şey yeniden bilinir .

C # 'ın yaptığı ve bir araya getirilmemiş bir şey için tamamen jenerik olmayan iki örnek için, yöntemleri ve bellek erişimini ele alalım.

OO dilleri genellikle yöntemlere sahiptir (ve bir sınıfa bağlı olmasa da benzer işlevlere sahip olmayan birçok ). Bu nedenle, böyle bir dilde bir yöntem tanımlayabilir, onu çağırabilir, belki de geçersiz kılabilirsiniz, vb. Tüm bu diller aslında bir programa veri olarak yöntemin kendisini ele almanıza izin vermez. C # (ve gerçekten, C # yerine .NET) MethodInfoyöntemleri temsil eden nesneleri kullanmanıza izin verir , bu nedenle C # yöntemleri yeniden birleştirilir. C # 'daki yöntemler "birinci sınıf nesneler" dir.

Tüm pratik dillerin bilgisayarın belleğine erişmek için bazı araçları vardır. C gibi düşük seviyeli bir dilde, bilgisayar tarafından kullanılan sayısal adresler arasındaki eşleme ile doğrudan başa çıkabiliriz, bu yüzden beğenileri int* ptr = (int*) 0xA000000; *ptr = 42;makul (bellek adresine erişmenin 0xA000000bu şekilde kazandığından şüphelenmek için iyi bir nedenimiz olduğu sürece) t bir şeyi havaya uçurmak). C # 'da bu makul değildir (hemen hemen .NET'te zorlayabiliriz, ancak .NET bellek yönetimi ile işleri hareket ettirmek yararlı olmayacaktır). C # 'ın bellek adresleri birleştirilmiş değil.

Dolayısıyla, refied anlamına gelen "gerçek yapmak" bir " birleşik tip" söz konusu dilde "hakkında konuşabileceğimiz" bir tiptir.

Jenerikte bu iki anlama gelir.

Bir olmasıdır List<string>tıpkı türüdür stringveya intbulunmaktadır. Bu türü karşılaştırabilir, adını alabilir ve sorgulayabiliriz:

Console.WriteLine(typeof(List<string>).FullName); // System.Collections.Generic.List`1[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
Console.WriteLine(typeof(List<string>) == (42).GetType()); // False
Console.WriteLine(typeof(List<string>) == Enumerable.Range(0, 1).Select(i => i.ToString()).ToList().GetType()); // True
Console.WriteLine(typeof(List<string>).GenericTypeArguments[0] == typeof(string)); // True

Bunun bir sonucu olarak, yöntemin kendisinde bir genel yöntemin (veya bir genel sınıf yönteminin) parametrelerinin türleri hakkında "konuşabiliriz":

public static void DescribeType<T>(T element)
{
  Console.WriteLine(typeof(T).FullName);
}
public static void Main()
{
  DescribeType(42);               // System.Int32
  DescribeType(42L);              // System.Int64
  DescribeType(DateTime.UtcNow);  // System.DateTime
}

Kural olarak, bunu çok fazla yapmak "koklamak" dır, ancak birçok yararlı vakası vardır. Örneğin, şuna bakın:

public static TSource Min<TSource>(this IEnumerable<TSource> source)
{
  if (source == null) throw Error.ArgumentNull("source");
  Comparer<TSource> comparer = Comparer<TSource>.Default;
  TSource value = default(TSource);
  if (value == null)
  {
    using (IEnumerator<TSource> e = source.GetEnumerator())
    {
      do
      {
        if (!e.MoveNext()) return value;
        value = e.Current;
      } while (value == null);
      while (e.MoveNext())
      {
        TSource x = e.Current;
        if (x != null && comparer.Compare(x, value) < 0) value = x;
      }
    }
  }
  else
  {
    using (IEnumerator<TSource> e = source.GetEnumerator())
    {
      if (!e.MoveNext()) throw Error.NoElements();
      value = e.Current;
      while (e.MoveNext())
      {
        TSource x = e.Current;
        if (comparer.Compare(x, value) < 0) value = x;
      }
    }
  }
  return value;
}

Bu türü arasındaki karşılaştırmalar çok yapmaz TSourceve farklı davranışları için çeşitli türleri (eğer hiç jeneriği kullanmış gerektiğini genellikle bir işareti) ama olabilir türleri için bir kod yolu arasındaki bölünmüş yapar null(dönmelidir nulleğer öğe bulunamadı ve karşılaştırılan öğelerden biri varsa minimum değeri bulmak için karşılaştırmalar yapmamalıdır null) ve türler için kod yolu bulunamaz null(hiçbir öğe bulunamazsa atılmalı ve nullöğelerin olasılığı hakkında endişelenmenize gerek yoktur) ).

TSourceYöntem içinde "gerçek" olduğundan , bu karşılaştırma çalışma zamanında veya jitting zamanında yapılabilir (genellikle jitting zamanı, kesinlikle yukarıdaki durum jitting zamanında bunu yapar ve alınmayan yol için makine kodu üretmez) ve bir her durum için yöntemin ayrı "gerçek" sürümü. (Bir optimizasyon olmasına rağmen, makine kodu, farklı referans tipi tip parametreleri için farklı yöntemler için paylaşılır, çünkü bunu etkilemeden olabilir ve dolayısıyla verilen makine kodu miktarını azaltabiliriz).

(Java ile de ilgilenmediğiniz sürece, C # 'da genel türlerin yeniden yapılandırılması hakkında konuşmak yaygın değildir, çünkü C #' da sadece bu yeniden yapılandırmayı kabul ediyoruz; tüm türler yeniden birleştirilir. Java'da, genel olmayan türler reified olarak adlandırılır, çünkü bunlar ve genel türler arasındaki bir ayrımdır).


MinYukarıdakileri yapmanın yararlı olduğunu düşünmüyor musunuz? Aksi halde belgelenmiş davranışını yerine getirmek çok zordur.
Jon Hanna

Ben hata (un) belgelenmiş davranış ve bu davranış yararlı bir sonuç olarak düşünün (bir kenara, davranışı, Enumerable.Min<TSource>boş bir koleksiyon referans olmayan türleri için atmaz, ancak varsayılan döndürür farklı (TSource) ve yalnızca "Genel bir sırada minimum değeri döndürür" olarak belgelenir. Her ikisinin de boş bir koleksiyon atması gerektiğini veya "sıfır" öğesinin temel olarak ve karşılaştırıcı / karşılaştırma işlevi her zaman iletilmelidir)
Martijn

1
Bu, geçerli olmayan türlerde imkansız denemeden null olabilecek türlerde ortak db davranışıyla eşleşen mevcut Min'den çok daha az yararlı olacaktır. (Temel fikir imkansız değildir, ancak kaynağında asla olmayacağını bildiğiniz bir değer olmadığı sürece çok yararlı değildir).
Jon Hanna

1
Bunun için daha iyi bir isim olurdu. :)
tchrist

@tchrist bir şey gerçek dışı olabilir.
Jon Hanna

15

Gibi duffymo zaten belirtildiği , "Şeyleşme" önemli fark yoktur.

Java'da, jenerikler derleme zamanı desteğini geliştirmek için temel olarak oradadır - kodunuzda güçlü yazılan örneğin koleksiyonları kullanmanıza ve sizin için tür güvenliğine sahip olmanıza olanak tanır. Bununla birlikte, bu sadece derleme zamanında mevcuttur - derlenmiş bayt kodunda artık herhangi bir jenerik kavramı yoktur; tüm genel türler "somut" türlere dönüştürülür ( objectgenel tür sınırsızsa kullanılır), gerektiğinde tür dönüşümleri ve tür denetimleri eklenir.

.NET'te, jenerikler CLR'nin ayrılmaz bir özelliğidir. Genel bir tür derlediğinizde, oluşturulan IL'de genel kalır. Sadece Java'da olduğu gibi jenerik olmayan koda dönüştürülmez.

Bunun jeneriklerin pratikte nasıl çalıştığı üzerinde birkaç etkisi vardır. Örneğin:

  • Java, SomeType<?>belirli bir genel türdeki herhangi bir somut uygulamayı geçirmenize izin vermelidir. C # bunu yapamaz - Her özgü ( şeyleşmiş ) genel tür kendi türüdür.
  • Java'daki sınırsız genel türler, değerlerinin bir object. Bu tür jeneriklerde değer türlerini kullanırken bunun performans etkisi olabilir. C # 'da, genel bir türde bir değer türü kullandığınızda, bir değer türü kalır.

Bir örnek vermek için, Listbir genel bağımsız değişkene sahip genel bir türünüz olduğunu varsayalım . Java'da List<String>ve List<Int>çalışma zamanında tam olarak aynı tür olacak - genel türler gerçekten derleme zamanı kodu için var. Örneğin tüm çağrılar GetValueiçin dönüşüm olacak (String)GetValueve (Int)GetValuesırasıyla.

C # List<string>ve List<int>iki farklı tür vardır. Değiştirilemezler ve tip güvenliği çalışma zamanında da uygulanır. Hayır, ne yaptığım önemli new List<int>().Add("SomeString")olacak asla işi - altında yatan depolama List<int>olduğunu gerçekten Java, mutlaka bir iken, bazı tamsayı dizisi objectdizi. C # 'da, hiçbir oyuncu kadrosu, boks vb. Yoktur.

Bu ayrıca, C # ile neden Java ile aynı şeyi yapamayacağını açıklığa kavuşturmalıdır SomeType<?>. Java'da, "türetilmiş" türetilmiş tüm genel türler SomeType<?>aynı türdür. C # 'da, tüm spesifik SomeType<T>s kendi ayrı tipleridir. Derleme zamanı denetimlerinin kaldırılması SomeType<Int>yerine geçmek mümkündür SomeType<String>(ve gerçekten de, SomeType<?>"verilen genel tür için derleme zamanı denetimlerini yoksay" anlamına gelir). C # 'da, türetilmiş türler için bile mümkün değildir (yani , türetilmiş List<object> list = (List<object>)new List<string>();olsa bile yapamazsınız ).stringobject

Her iki uygulamanın da artıları ve eksileri vardır. Sadece SomeType<?>C # 'da bir argüman olarak izin verebilmek isterdim birkaç kez oldu - ama sadece C # jeneriklerinin çalışma şekli mantıklı değil.


2
Eh, türler yararlanabilir List<>, Dictionary<,>ve böylece C # üzerinde, ama bu ve belirli somut liste veya sözlük arasındaki boşluk köprüye yansıması biraz sürer. Arayüzlerdeki varyans, bir zamanlar bu boşluğu kolayca köprülemek isteyebileceğimiz bazı durumlarda yardımcı olur, ancak hepsi değil.
Jon Hanna

2
@JonHanna List<>Yeni bir belirli genel tür başlatmak için kullanabilirsiniz - ancak yine de istediğiniz belirli türü oluşturmak anlamına gelir. Ancak List<>, örneğin bir argüman olarak kullanamazsınız . Ama evet, en azından bu, yansımayı kullanarak boşluğu kapatmanıza izin veriyor.
Luaan

.NET Framework'te, depolama konumu türleri olmayan üç sabit kodlu genel kısıtlama vardır; diğer tüm kısıtlamalar depolama yeri türleri olmalıdır. Ayrıca, genel bir türün Tdepolama-konum-tipi bir kısıtlamayı karşılayabildiği zamanlar aynı ve Une zaman aynıdır, ya da bir örneğine başvuru yapabilen bir türdür . Anlamlı bir şekilde depo yeri tipine sahip olmak mümkün olmayacaktır, ancak teorik olarak bu tipte genel bir kısıtlamaya sahip olmak mümkün olacaktır. TUUTSomeType<?>
supercat

1
Derlenmiş Java bayt kodunun jenerik kavramının olmadığı doğru değildir. Sadece sınıf örneklerinin jenerik kavramları yoktur. Bu önemli bir farktır; İlginizi çekiyorsa, bu konuda daha önce programcılar.stackexchange.com/ questions/280169/… 'da yazmıştım .
ruakh

2

Reification, nesne yönelimli bir modelleme konseptidir.

Reify, "soyut bir şeyi gerçek yapmak" anlamına gelen bir fiildir .

Nesneye yönelik programlama yaptığınızda, gerçek dünya nesnelerini yazılım bileşenleri (örneğin Pencere, Düğme, Kişi, Banka, Araç vb.) Olarak modellemek yaygındır.

Soyut kavramları bileşenlere de eklemek yaygındır (örn. WindowListener, Broker vb.)


2
İlişkilendirme genel olarak "gerçek olan bir şeyi yapma" kavramıdır, dediğiniz gibi nesne yönelimli modelleme için geçerli olsa da, jeneriklerin uygulanması bağlamında da bir anlamı vardır.
Jon Hanna

2
Bu yüzden bu cevapları okuyarak eğitim aldım. Cevabımı değiştireceğim.
duffymo

2
Bu cevap OP'nin jenerik ve parametrik polimorfizme ilgisini ele almak için hiçbir şey yapmaz.
Erick G. Hagstrom

Bu yorum kimsenin ilgisini çekmek veya temsilcinizi artırmak için hiçbir şey yapmaz. Bakın hiçbir şey teklif etmediniz. Benim ilk cevabımdı ve bu, birleşmeyi daha geniş bir şey olarak tanımladı.
duffymo

1
Cevabınız ilk olabilir, ancak OP tarafından sorulan değil, sorunun içeriğinden ve etiketlerinden açık olan farklı bir soruyu cevapladınız. Belki cevabınızı yazmadan önce soruyu tam olarak okumadınız, ya da belki de "yeniden birleşme" teriminin jenerikler bağlamında yerleşik bir anlamı olduğunu bilmiyordunuz. Her iki durumda da cevabınız yararlı değil. Downvote.
jcsahnwaldt Reinstate Monica
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.