Genel türlerden miras almak iyi bir uygulama mıdır?


35

List<string>Tür ek açıklamalarında veya StringListyerde kullanmak daha mı iyiStringList

class StringList : List<String> { /* no further code!*/ }

Ben koştu birkaç ait bu yer Irony .


Genel türlerden miras alamazsanız, neden oradalar?
Mawg

7
@Mawg Yalnızca örnekleme için kullanılabilirler.
Theodoros Chatzigiannakis,

3
Bu kodda görmek benim en sevdiğim şeylerden biridir
Kik

4
Yuck. Yok gerçekten. Arasındaki anlamsal fark nedir StringListve List<string>? Yok, henüz (genel "siz") yalnızca projenize özgü olan kod tabanına başka bir ayrıntı eklemeyi başardınız ve kodu çalışıncaya kadar başkasına hiçbir şey ifade etmiyorsunuz. Neden sadece bir dizi listesi olarak çağırmak için bir dizi listesinin ismini maskeleyelim? Öte yandan, yapmak istersen BagOBagels : List<string>bunun daha anlamlı olacağını düşünüyorum. Bunu sayısız olarak yineleyebilirsiniz, dış tüketiciler uygulama ile ilgilenmez - her şey yolunda.
Craig

1
XAML'ın jenerik kullanamayacağınız bir sorunu var ve bir listeyi doldurmak için böyle bir tür yapmanız gerekiyordu
Daniel Little

Yanıtlar:


27

Genel bir türden miras almak isteyebileceğiniz başka bir neden var.

Microsoft , yöntem imzalarında genel türlerin yuvalanmasından kaçınılmasını önerir .

Öyleyse, jeneriklerinizden bazıları için işletme-etki alanı tipi oluşturmak iyi bir fikirdir. Bir sahip olmak yerine, IEnumerable<IDictionary<string, MyClass>>bir tür oluşturun MyDictionary : IDictionary<string, MyClass>ve IEnumerable<MyDictionary>üyeniz çok daha okunaklı olan bir olur . Ayrıca, MyDictionary'yi bir çok yerde kullanmanıza izin vererek kodunuzun açıklığını artırır.

Bu, çok büyük bir avantaj gibi görünmeyebilir, ancak gerçek dünyadaki iş kodunda saçınızı ayakta tutacak jenerik ilaçlar gördüm. Gibi şeyler List<Tuple<string, Dictionary<int, List<Dictionary<string, List<double>>>>>. Bu jenerikin anlamı hiçbir şekilde açık değildir. Yine de, bir dizi açıkça yaratılmış türden yapılmışsa, orijinal geliştiricinin niyeti daha açık olacaktır. Dahası, eğer bu genel türün bir sebepten dolayı değişmesi gerekiyorsa, bu genel türün tüm örneklerini bulmak ve değiştirmek, türetilmiş sınıfta değiştirmekle karşılaştırıldığında, gerçek bir acı olabilir.

Son olarak, birisinin jeneriklerden türetilmiş kendi türlerini oluşturmak istemesinin başka bir nedeni var. Aşağıdaki sınıfları göz önünde bulundurun:

public class MyClass
{
    public ICollection<MyItems> MyItems { get; private set; }
    // plumbing code here
}

public class MyOtherClass
{
    public ICollection<MyItems> MyItemCache { get; private set; }
    // plumbing code here
}

public class MyConsumerClass
{
    public MyConsumerClass(ICollection<MyItems> myItems)
    {
        // use the collection
    }
}

Şimdi ICollection<MyItems>, MyConsumerClass için hangi kurucuya geçileceğini nasıl bilebiliriz? Biz türetilmiş sınıfları oluşturursanız class MyItems : ICollection<MyItems>ve class MyItemCache : ICollection<MyItems>, MyConsumerClass sonra aslında istediği iki koleksiyonları hangi derece spesifik olabilir. Bu daha sonra, iki koleksiyonu çok kolay çözebilen bir IoC kabı ile çok iyi çalışır. Ayrıca programcının daha doğru olmasını sağlar - MyConsumerClassherhangi bir ICollection ile çalışmak mantıklıysa , genel yapıcıyı alabilirler. Bununla birlikte, türetilmiş iki türden birini kullanmak hiçbir zaman mantıklı olmazsa, geliştirici constructor parametresini belirli türle sınırlayabilir.

Özet olarak, türleri genel türlerden türetmek faydalı olacaktır - uygun olan yerlerde daha açık kod sağlar ve okunabilirlik ve bakım kolaylığı sağlar.


12
Bu kesinlikle saçma bir Microsoft önerisi.
Thomas Eding

7
Herkese açık API'lar için saçma olduğunu sanmıyorum. İç içe jeneriklerin bir bakışta anlaşılması zordur ve daha anlamlı bir tür ad genellikle okunabilirliğe yardımcı olabilir.
Stephen

4
Saçma olduğunu sanmıyorum ve özel API'ler için kesinlikle sorun yok ... ama halka açık API'ler için bunun iyi bir fikir olduğunu sanmıyorum. Microsoft bile, genel tüketim amaçlı API'ler yazarken en temel türlerin kabul edilmesini ve geri gönderilmesini önermektedir.
Paul d'Aoust

35

Prensip olarak, genel türlerden miras almak ile başka herhangi bir şeyden miras almak arasında bir fark yoktur.

Ancak, neden dünyadaki herhangi bir sınıftan türetiyor ve başka bir şey eklemiyorsunuz?


17
Katılıyorum. Hiçbir şeye katkıda bulunmadan "StringList" okur ve kendi kendime " Gerçekten ne yapar ?" Diye düşünürdüm.
Neil,

1
Ben de kabul ediyorum, belki de "uzatmak" anahtar kelimesini kullanıp miras almanız durumunda, bir sınıfa geçecek olursanız, daha fazla işlevsellik ile genişletmeniz gerektiğine dair iyi bir hatırlatmadır.
Elliot Blackburn,

14
-1, bunun farklı bir isim seçerek bir soyutlama yaratmanın sadece bir yolu olduğunu anlamadığınız için - soyutlamalar oluşturmak, bu sınıfa hiçbir şey eklememek için çok iyi bir neden olabilir.
Doktor Brown,

1
Cevabımı düşünün, birilerinin bunu yapmaya karar vermesinin bazı sebeplerine giriyorum.
NPSF3000

1
"neden dünyadaki herhangi bir sınıftan türetiyor ve başka bir şey eklemiyorsun?" Kullanıcı kontrolleri için yaptım. Genel bir kullanıcı denetimi oluşturabilirsiniz, ancak bunu bir windows form tasarımcısında kullanamazsınız, bu nedenle ondan miras almanız, türünü belirtmeniz ve onu kullanmanız gerekir.
George T

13

C # 'yi çok iyi tanımıyorum, ancak typedefC ++ programcılarının genellikle birkaç sebepten dolayı kullandıkları tek yolun bu olduğuna inanıyorum :

  • Kodunuzun açılı ayraçlar gibi görünmesini önler.
  • İhtiyaçlarınız sonradan değişirse farklı altta bulunan kapları değiştirmenize izin verir ve bunu yapma hakkını saklı tuttuğunuzu açıkça belirtir.
  • Bağlamda daha alakalı bir ad girmenizi sağlar.

Temelde sıkıcı ve genel isimleri seçerek son avantajı attılar, ancak diğer iki neden hala geçerli.


7
Eh ... Onlar tamamen aynı değil. C ++ 'da değişmez takma ismin vardır. StringList olan bir List<string>- onlar birbirlerinin yerine kullanılabilir. Bu, dünyadaki diğer herkes tarafından kullanıldığından, diğer API'lerle çalışırken (veya API'nizi gösterirken) önemlidir List<string>.
Telastyn

2
Aynı şey olmadıklarının farkındayım. Mümkün olduğu kadar yakın. Zayıf versiyonun kesinlikle sakıncaları var.
Karl Bielefeldt

24
Hayır, using StringList = List<string>;typedef'in en yakın C # eşdeğeridir. Bunun gibi alt sınıflandırma, yapıcıların argümanlarla kullanılmasını önler.
Pete Kirkham

4
@PeteKirkham: Bir StringList tanımlayarak usingonu usingyerleştirildiği kaynak kod dosyası ile sınırlandırır . Bu açıdan, mirasa daha yakındır typedef. Ve bir alt sınıf oluşturmak, gerekli tüm yapıcıları eklemeyi engellemez (sıkıcı olsa da).
Doktor Brown,

2
@ Random832: Bunu farklı bir şekilde ifade edeyim: C ++ 'da bir tek adı bir yere atamak ve onu "tek bir gerçek kaynağı" yapmak için bir typedef kullanılabilir . C # 'da, usingbu amaç için kullanılamaz, ancak kalıtım olabilir. Bu neden Pete Kirkham'ın haksız yere yorumuna katılmıyorum - usingC ++ 'nın gerçek bir alternatifi olduğunu düşünmüyorum typedef.
Doktor Brown,

9

En az bir pratik sebep düşünebiliyorum.

Genel türlerden miras kalan (başka bir şey eklemeden bile) genel olmayan türlerin bildirilmesi, bu türlerin, aksi durumda kullanılamayacakları veya olması gerektiğinden daha karmaşık bir şekilde kullanılabileceği yerlerden kaynak gösterilmesini sağlar.

Bu tür bir durum, yalnızca genel olmayan türlerin kullanılabildiği yerlerde Visual Studio'daki Ayarlar editörü ile ayarları eklerken olur. Oraya giderseniz, tüm System.Collectionssınıfların uygun olduğunu göreceksiniz ancak System.Collections.Genericgeçerli olduğu için hiçbir koleksiyon görünmüyor.

Diğer bir durum ise WPF'deki türlerin XAML aracılığıyla başlatılmasıdır. 2009 öncesi şema kullanıldığında XML sözdiziminde tür argümanlarının iletilmesi için destek yoktur. Bu, 2006 şemasının yeni WPF projeleri için varsayılan gibi görünmesi gerçeğiyle daha da kötüleşmiştir.


1
WCF web servis arayüzlerinde daha iyi görünümlü koleksiyon tipi isimleri (örneğin, ArrayOfPerson yerine PersonCollection veya her neyse) sağlamak için bu tekniği kullanabilirsiniz
Rico Suter

7

Bence bazı tarihlere bakmalısın .

  • C #, genel türleri olduğundan daha uzun süredir kullanılıyor.
  • Verilen yazılımın tarihin bir noktasında kendi StringList'ine sahip olmasını bekliyorum.
  • Bu, bir ArrayList'i sararak da uygulanmış olabilir.
  • Ardından kod tabanını ileri taşımak için refactoring yapan biri.
  • Ancak 1001 farklı dosyaya dokunmak istemiyordu, bu yüzden işi bitirin.

Aksi takdirde, Theodoros Chatzigiannakis en iyi cevaplara sahipti; örneğin, genel türleri anlamayan pek çok araç var. Veya belki de cevabını ve tarihini bir karışımı.


3
"C #, genel türleri olduğundan daha uzun süredir kullanılıyor." Gerçekten de, jenerik olmayan C # yaklaşık 4 yıl (Ocak 2002 - Kasım 2005), jenerikli C # ise 9 yaşındaydı.
svick


4

Bazı durumlarda, genel türlerin kullanılması mümkün değildir; örneğin, XAML'deki genel bir türe başvuruda bulunamazsınız. Bu durumlarda, başlangıçta kullanmak istediğiniz genel türden miras alan genel olmayan bir tür oluşturabilirsiniz.

Mümkünse, bundan kaçınırdım.

Bir typedef'in aksine, yeni bir tip yaratırsınız. StringList'i bir yönteme giriş parametresi olarak kullanırsanız, arayanlarınız a geçemez List<string>.

Ayrıca, kullanmak istediğiniz tüm yapıcıları , böyle bir yapıcıyı tanımlamasına new StringList(new[]{"a", "b", "c"})rağmen , StringList örneğinde söyleyemediğiniz bir örneği açıkça kopyalamanız gerekir List<T>.

Düzenle:

Kabul edilen cevap etki alanı modelinizde türetilmiş türleri kullanırken profesyonellerin üzerinde durur. Bu durumda potansiyel olarak yararlı olabileceklerini kabul ediyorum, yine de bizzat orada bile onlardan kaçınırdım.

Ancak, (bence) bunları asla halka açık bir API'de kullanmamalısınız, çünkü ya arayanızın hayatını zorlaştırır ya da israf performansını (genel olarak örtük dönüşümleri de pek sevmem).


3
" XAML'da genel bir türe başvuru yapamazsınız " diyorsunuz , ancak neyi kastettiğinizden emin değilim.
XAML'deki

1
Bu bir örnek olarak ifade edildi. Evet, 2009 XAML Şeması ile mümkündür, ancak AFAIK, hala VS varsayılanı olan 2006 Şeması ile değildir.
Lukas Rieger

1
Üçüncü paragrafınız sadece yarısı doğrudur, buna gerçekten dolaylı dönüştürücülerle izin verebilirsiniz;)
Knerd

1
@Knerd, true, ama sonra 'dolaylı olarak' yeni bir nesne yaratırsınız. Çok fazla iş yapmazsanız, bu aynı zamanda verilerin bir kopyasını da yaratacaktır. Muhtemelen küçük listelerle bir önemi yoktur, ancak birisi birkaç bin öğeden oluşan bir listeyi geçerse eğlenin =)
Lukas Rieger

@LukasRieger haklısın: PI sadece şunu belirtmek isterdim: P
Knerd

2

Buna değer, Microsoft’un Roslyn derleyicisinde ve sınıfın adını bile değiştirmeden, genel bir sınıftan miras almanın bir örneği. (Bundan gerçekten şaşırmıştım, bunun gerçekten mümkün olup olmadığını görmek için araştırmamda sona erdi.)

CodeAnalysis projesinde bu tanımı bulabilirsiniz:

/// <summary>
/// Common base class for C# and VB PE module builder.
/// </summary>
internal abstract class PEModuleBuilder<TCompilation, TSourceModuleSymbol, TAssemblySymbol, TTypeSymbol, TNamedTypeSymbol, TMethodSymbol, TSyntaxNode, TEmbeddedTypesManager, TModuleCompilationState> : CommonPEModuleBuilder, ITokenDeferral
    where TCompilation : Compilation
    where TSourceModuleSymbol : class, IModuleSymbol
    where TAssemblySymbol : class, IAssemblySymbol
    where TTypeSymbol : class
    where TNamedTypeSymbol : class, TTypeSymbol, Cci.INamespaceTypeDefinition
    where TMethodSymbol : class, Cci.IMethodDefinition
    where TSyntaxNode : SyntaxNode
    where TEmbeddedTypesManager : CommonEmbeddedTypesManager
    where TModuleCompilationState : ModuleCompilationState<TNamedTypeSymbol, TMethodSymbol>
{
  ...
}

Sonra CSharpCodeanalysis projesinde bu tanım var:

internal abstract class PEModuleBuilder : PEModuleBuilder<CSharpCompilation, SourceModuleSymbol, AssemblySymbol, TypeSymbol, NamedTypeSymbol, MethodSymbol, SyntaxNode, NoPia.EmbeddedTypesManager, ModuleCompilationState>
{
  ...
}

Bu jenerik olmayan PEModuleBuilder sınıfı, CSharpCodeanalysis projesinde yaygın olarak kullanılır ve bu projedeki birkaç sınıf doğrudan veya dolaylı olarak ondan miras alır.

Ve sonra BasicCodeanalysis projesinde şu tanım var:

Partial Friend MustInherit Class PEModuleBuilder
    Inherits PEModuleBuilder(Of VisualBasicCompilation, SourceModuleSymbol, AssemblySymbol, TypeSymbol, NamedTypeSymbol, MethodSymbol, SyntaxNode, NoPia.EmbeddedTypesManager, ModuleCompilationState)

İnşallah (umarım) Roslyn'in geniş C # bilgisi olan insanlar tarafından yazıldığını ve nasıl kullanılması gerektiğini varsayalım. Bunun bir teknik öneri olduğunu düşünüyorum.


Evet. Ve ayrıca, beyan edildiklerinde doğrudan yan tümce maddelerini daraltmak için kullanılabilirler.
Sentinel

1

Birisinin bunu kafamın üstünden çıkarmaya çalışmasının üç olası nedeni var:

1) Bildirimleri basitleştirmek için:

Elbette StringListve ListStringyaklaşık aynı uzunluktadırlar, ancak bunun yerine UserCollection, aslında Dictionary<Tuple<string,Type>, IUserData<Dictionary,MySerializer>>ya da başka bir büyük genel örnekle çalıştığınızı hayal edin .

2) AOT'a yardım etmek için:

Bazen, Tam Zamanında derlemeyi değil, Zaman İçi derlemesini değil, örneğin iOS için geliştirmeyi kullanmanız gerekir. Bazen AOT derleyicisi önceden tüm jenerik türleri bulmakta zorlanıyor ve bu bir ipucu sağlama girişimi olabilir.

3) İşlevsellik eklemek veya bağımlılığı kaldırmak için:

Belki de uygulamak istedikleri StringList(uzatma yönteminde gizlenmiş, henüz eklenmemiş veya tarihi geçmiş olabilir) ya da List<string>miras almadan ekleyemeyecekleri ya da onunla kirletmek istemedikleri belirli bir işlevi List<string>vardır. .

Alternatif olarak StringList, pistin aşağı doğru uygulanmasını değiştirmek isteyebilirler , bu yüzden sınıfı sadece bir işaretleyici olarak kullandım (genellikle bunun için arayüzler yaparsınız / kullanırsınız IList).


Ayrıca, bu kodu bir dil çözümleyici olan Irony'de bulduğunuzu da unutmayın - oldukça sıradışı bir uygulama. Bunun kullanılmasının özel bir nedeni olabilir.

Bu örnekler, böyle bir bildiri yazmak için meşru sebepler olabileceğini göstermektedir - çok yaygın olmasa bile. Her şeyde olduğu gibi birkaç seçenek düşünün ve o sırada sizin için en uygun olanı seçin.


Kaynak koduna bir göz attıysanız, seçenek 3 görünür - nedeni budur; işlevsellik eklemek / koleksiyonları özelleştirmek için bu tekniği kullanırlar:

public class StringList : List<string> {
    public StringList() { }
    public StringList(params string[] args) {
      AddRange(args);
    }
    public override string ToString() {
      return ToString(" ");
    }
    public string ToString(string separator) {
      return Strings.JoinStrings(separator, this);
    }
    //Used in sorting suffixes and prefixes; longer strings must come first in sort order
    public static int LongerFirst(string x, string y) {
      try {//in case any of them is null
        if (x.Length > y.Length) return -1;
      } catch { }
      if (x == y) return 0;
      return 1; 
    }

1
Bazen bildirimleri basitleştirme (veya her şeyi basitleştirme) arayışı, azaltılmış karmaşıklıktan ziyade, artan karmaşıklıkla sonuçlanabilir. Dili iyi bilen herkes ne olduğunu iyi bilir List<T>, ancak StringListgerçekte ne olduğunu anlamak için bağlamında incelemeleri gerekir . Gerçekte, sadece List<string>farklı bir isimden geçiyor. Bu kadar basit bir durumda bunu yapmanın anlamsal bir avantajı olmadığı görülüyor. Gerçekten sadece iyi bilinen bir türü, aynı tipte farklı bir isimle değiştiriyorsunuz.
Craig

@Craig, usecase (daha uzun bildirimler) ve diğer olası sebeplerin açıklanmasından bahsettiğim gibi hemfikirim.
NPSF3000

-1

Bunun iyi bir uygulama olmadığını söyleyebilirim.

List<string>"genel bir dizge listesi" iletişim kurar. Açıkça List<string> uzantı yöntemleri uygulanır. Anlamak için daha az karmaşıklık gerekir; sadece Liste gereklidir.

StringList"ayrı bir sınıfa ihtiyaç" olduğunu bildirir. Belki List<string> uzatma yöntemleri de geçerlidir (bunun için gerçek tip uygulamasını kontrol edin). Kodu anlamak için daha fazla karmaşıklık gerekir; Liste ve türetilmiş sınıf uygulama bilgisi gereklidir.


4
Uzatma yöntemleri yine de uygulanır.
Theodoros Chatzigiannakis,

2
Ders dışı. Ancak, StringList'i kontrol edip türetilene kadar göremezsiniz List<string>.
Ruudjah

@TheodorosChatzigiannakis Ruudjah'ın "uzatma yöntemleri uygulanmadığı" anlamında ne kadar ayrıntılı olabileceğini merak ediyorum. Uzatma yöntemleri herhangi bir sınıfta bir olasılıktır . Tabii ki, .NET framework kütüphanesi, genel toplama sınıfları için geçerli olan LINQ denilen bir çok uzatma yöntemiyle birlikte gelir, ancak bu, uzatma yöntemlerinin başka hiçbir sınıfa uygulanmadığı anlamına gelmez mi? Belki Ruudjah ilk bakışta açık olmayan bir noktaya değiniyordur? ;-)
Craig

"uzatma yöntemleri geçerli değil." Bunu nerede okuyorsun? Diyorum ki "uzatma yöntemleri geçerli olabilir .
Ruudjah

1
Tip uygulaması size uzatma yöntemlerinin uygulanıp uygulanmadığını söylemeyecektir, öyle değil mi? O yöntemleri sınıf ortalamaları uzantısı Sadece gerçeği do geçerlidir. Türünüzün herhangi bir tüketicisi kodunuzdan tamamen bağımsız olarak uzatma yöntemleri oluşturabilir. Sanırım ne alıyorum ya da soruyorum. Sadece LINQ hakkında mı konuşuyorsun? LINQ, uzatma yöntemleri kullanılarak uygulanır . Ancak, belirli bir tür için LINQ'ye özgü uzantı yöntemlerinin olmaması, bu tür için uzantı yöntemlerinin bulunmadığı veya olmayacağı anlamına gelmez. Herhangi bir zamanda herhangi biri tarafından yaratılabilirler.
Craig
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.