È buona norma ereditare da tipi generici?

30

È meglio usare List<string> nelle annotazioni del tipo o StringList dove StringList

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

Mi sono imbattuto in diversi di questi in ironia .

    
posta Ruudjah 17.12.2014 - 11:55
fonte

9 risposte

25

C'è un altro motivo per cui potresti voler ereditare da un tipo generico.

Microsoft consiglia evitando di annidare i tipi generici nelle firme dei metodi .

Quindi è una buona idea creare tipi di nomi di dominio aziendale per alcuni dei tuoi generici. Invece di avere un IEnumerable<IDictionary<string, MyClass>> , crea un tipo MyDictionary : IDictionary<string, MyClass> e poi il tuo membro diventa IEnumerable<MyDictionary> , che è molto più leggibile. Consente inoltre di utilizzare MyDictionary in un numero di punti, aumentando l'esplicitazione del codice.

Questo potrebbe non sembrare un enorme vantaggio, ma nel codice commerciale del mondo reale ho visto generici che ti faranno rizzare i capelli. Cose come List<Tuple<string, Dictionary<int, List<Dictionary<string, List<double>>>>> . Il significato di questo generico non è in alcun modo ovvio. Tuttavia, se fosse stato costruito con una serie di tipi creati esplicitamente, l'intenzione dello sviluppatore originale sarebbe molto più chiara. Inoltre, se quel tipo generico dovesse cambiare per qualche motivo, trovare e sostituire tutte le istanze di quel tipo generico potrebbe essere un vero problema, rispetto a cambiarlo nella classe derivata.

Infine, c'è un'altra ragione per cui qualcuno potrebbe desiderare di creare i propri tipi derivati da generici. Considera le seguenti classi:

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
    }
}

Ora come facciamo a sapere quale ICollection<MyItems> deve passare nel costruttore per MyConsumerClass? Se creiamo classi derivate class MyItems : ICollection<MyItems> e class MyItemCache : ICollection<MyItems> , MyConsumerClass può quindi essere estremamente specifico su quale delle due raccolte desideri effettivamente. Quindi funziona molto bene con un contenitore IoC, che può risolvere le due raccolte molto facilmente. Inoltre, consente al programmatore di essere più preciso - se ha senso che MyConsumerClass funzioni con qualsiasi ICollection, possono prendere il costruttore generico. Se, tuttavia, non ha mai senso per il business utilizzare uno dei due tipi derivati, lo sviluppatore può limitare il parametro costruttore al tipo specifico.

In sintesi, è spesso utile ricavare tipi da tipi generici: consente di ottenere un codice più esplicito laddove appropriato e agevola la leggibilità e la gestibilità.

    
risposta data 18.12.2014 - 04:12
fonte
35

In linea di principio non c'è differenza tra ereditare da tipi generici e ereditare da qualsiasi altra cosa.

Tuttavia, perché mai dovresti derivare dalla classe any e non aggiungere altro?

    
risposta data 17.12.2014 - 12:01
fonte
13

Non conosco molto bene C #, ma credo che questo sia l'unico modo per implementare un typedef , che i programmatori C ++ usano comunemente per alcuni motivi:

  • Mantiene il tuo codice simile a un mare di parentesi angolari.
  • Ti consente di scambiare diversi contenitori sottostanti se le tue esigenze cambiano in un secondo momento e chiarisci che ti riservi il diritto di farlo.
  • Ti consente di attribuire a un tipo un nome più pertinente nel contesto.

In pratica hanno buttato via l'ultimo vantaggio scegliendo nomi noiosi e generici, ma gli altri due motivi continuano a sussistere.

    
risposta data 17.12.2014 - 14:38
fonte
9

Posso pensare ad almeno un motivo pratico.

Dichiarando tipi non generici che ereditano da tipi generici (anche senza aggiungere nulla), consente di fare riferimento a quei tipi da luoghi in cui altrimenti non sarebbero disponibili o disponibili in un modo più complesso di quanto dovrebbero.

Uno di questi casi è quando si aggiungono le impostazioni tramite l'editor Impostazioni in Visual Studio, in cui solo i tipi non generici sembrano essere disponibili. Se vai lì, noterai che tutte le classi System.Collections sono disponibili, ma nessuna raccolta da System.Collections.Generic appare come applicabile.

Un altro caso è quando si istanziano i tipi tramite XAML in WPF. Non è supportato il passaggio degli argomenti di tipo nella sintassi XML quando si utilizza uno schema pre-2009. Ciò è aggravato dal fatto che lo schema del 2006 sembra essere l'impostazione predefinita per i nuovi progetti WPF.

    
risposta data 17.12.2014 - 16:40
fonte
7

Penso che sia necessario esaminare alcuni cronologia .

  • C # è stato utilizzato per un periodo di tempo molto più lungo di quello dei tipi generici per.
  • Mi aspetto che il software fornito abbia la sua StringList ad un certo punto nella storia.
  • Questo potrebbe essere stato implementato avvolgendo un ArrayList.
  • Poi qualcuno a refactoring per spostare il code base in avanti.
  • Ma non volevo toccare 1001 file diversi, quindi finire il lavoro.

In caso contrario, Theodoros Chatzigiannakis ha avuto le risposte migliori, ad es. ci sono ancora molti strumenti che non comprendono i tipi generici. O forse anche un mix della sua risposta e della sua storia.

    
risposta data 17.12.2014 - 16:59
fonte
4

In alcuni casi è impossibile utilizzare tipi generici, ad esempio non è possibile fare riferimento a un tipo generico in XAML. In questi casi è possibile creare un tipo non generico che eredita dal tipo generico che si desiderava utilizzare in origine.

Se possibile, lo eviterei.

A differenza di un typedef, puoi creare un nuovo tipo. Se si utilizza StringList come parametro di input per un metodo, i chiamanti non possono passare un List<string> .

Inoltre, dovresti copiare esplicitamente tutti i costruttori che vuoi usare, nell'esempio StringList non puoi dire new StringList(new[]{"a", "b", "c"}) , anche se List<T> definisce tale costruttore.

Modifica

La risposta accettata si concentra sui professionisti quando utilizzano i tipi derivati all'interno del modello di dominio. Sono d'accordo sul fatto che possano potenzialmente essere utili in questo caso, tuttavia, personalmente li eviterei anche lì.

Ma dovresti (a mio parere) non usarli mai in un'API pubblica perché rendi la vita del chiamante più difficile o sprechi le prestazioni (inoltre non mi piacciono le conversioni implicite in generale).

    
risposta data 17.12.2014 - 16:50
fonte
1

Per quello che vale, ecco un esempio di come l'ereditarietà da una classe generica viene utilizzata nel compilatore Roslyn di Microsoft e senza nemmeno modificare il nome della classe. (Ero così sconcertato da questo che sono finito qui nella mia ricerca per vedere se fosse davvero possibile.)

Nel progetto CodeAnalysis puoi trovare questa definizione:

/// <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>
{
  ...
}

Quindi nel progetto CSharpCodeanalysis c'è questa definizione:

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

Questa classe non generica di PEModuleBuilder è ampiamente utilizzata nel progetto CSharpCodeanalysis e diverse classi in quel progetto ereditano da esso, direttamente o indirettamente.

E poi nel progetto BasicCodeanalysis c'è questa definizione:

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

Dato che possiamo (si spera) assumere che Roslyn sia stato scritto da persone con una vasta conoscenza di C # e come dovrebbe essere usato, sto pensando che questa sia una raccomandazione della tecnica.

    
risposta data 14.02.2018 - 11:54
fonte
0

Ci sono tre possibili motivi per cui qualcuno proverebbe a farlo in cima alla mia testa:

1) Per semplificare le dichiarazioni:

Certo StringList e ListString hanno all'incirca la stessa lunghezza, ma immagina invece di lavorare con un UserCollection che in realtà è Dictionary<Tuple<string,Type>, IUserData<Dictionary,MySerializer>> o qualche altro generico esempio.

2) Per aiutare AOT:

A volte è necessario utilizzare la compilazione di Ahead of Time e la compilazione Just In Time - ad es. in via di sviluppo per iOS. A volte il compilatore AOT ha difficoltà a trovare tutti i tipi generici prima del tempo, e questo potrebbe essere un tentativo di fornirgli un suggerimento.

3) Per aggiungere funzionalità o rimuovere la dipendenza:

Forse hanno funzionalità specifiche che vogliono implementare per StringList (potrebbero essere nascoste in un metodo di estensione, non ancora aggiunto, o storico) che non possono aggiungere a List<string> senza ereditarne, o che non vogliono inquinare List<string> con.

In alternativa, potrebbero voler cambiare l'implementazione di StringList lungo la traccia, quindi per ora hai usato la classe come marcatore (di solito userai / userai le interfacce per esempio IList ).

Vorrei anche notare che hai trovato questo codice in Irony, che è un parser di lingua - un tipo piuttosto insolito di applicazione. Potrebbe esserci qualche altro motivo specifico per cui è stato utilizzato.

Questi esempi dimostrano che ci possono essere motivi legittimi per scrivere una tale dichiarazione, anche se non è troppo comune. Come con qualsiasi cosa prendere in considerazione diverse opzioni e scegliere quello che è meglio per te in quel momento.

Se dai un'occhiata al codice sorgente appare l'opzione 3 è la ragione: usano questa tecnica per aiutare ad aggiungere funzionalità / specializzare le raccolte:

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; 
    }
    
risposta data 17.12.2014 - 23:45
fonte
-1

Direi che non è una buona pratica.

List<string> comunica "un elenco generico di stringhe". Si applicano i metodi di estensione chiaramente List<string> . Meno complessità è richiesta per capire; è necessario solo Elenco.

StringList comunica "un'esigenza per una classe separata". È possibile applicare i metodi di estensione di List<string> (verificare l'effettiva implementazione del tipo per questo). È necessaria più complessità per capire il codice; È richiesto l'elenco e la conoscenza dell'implementazione della classe derivata.

    
risposta data 17.12.2014 - 14:32
fonte

Leggi altre domande sui tag