2010-04-21 31 views
21

Quali sono gli usi chiave di una classe generica statica in C#? Quando dovrebbero essere usati? Quali esempi illustrano meglio il loro utilizzo?Utilizza per classi generiche statiche?

ad es.

public static class Example<T> 
{ 
    public static ... 
} 

Poiché non è possibile definire metodi di estensione in essi, essi sembrano essere in qualche modo limitati nella loro utilità. I riferimenti Web sull'argomento sono scarsi così chiaramente che non ci sono molte persone che li usano. Ecco un paio: -

http://ayende.com/Blog/archive/2005/10/05/StaticGenericClass.aspx

Static Generic Class as Dictionary


Sintesi delle risposte date

Le questioni chiave sembrano essere "Qual è la differenza tra una classe generica statica con statico metodi e una classe statica non generica con membri generici statici? "

La decisione su quale utilizzare sembra ruotare attorno "La classe deve memorizzare internamente uno stato specifico del tipo?"

Se non è necessaria la memoria interna specifica del tipo, è preferibile una classe statica non generica con metodi statici generici perché la sintassi di chiamata è più gradevole ed è possibile definire all'interno metodi di estensione.

risposta

18

Uso classi statiche generiche per il codice di riflessione pesante in cache.

Diciamo che ho bisogno di costruire un albero di espressioni che istanzia oggetti. Lo compilo una volta nel costruttore statico della classe, lo compilo a un'espressione lambda, quindi lo metto in cache in un membro della classe statica. Spesso non considero queste classi pubblicamente valutabili - di solito sono aiutanti per altre classi. Memorizzando le mie espressioni in questo modo, evito la necessità di memorizzare le mie espressioni in una sorta di Dictionary<Type, delegate>.

V'è un esempio di questo modello nel BCL. I metodi di estensione (DataRow) Field<T>() e SetField<T>() utilizzano la classe generica statica (privata) System.Data.DataRowExtensions+UnboxT<T>. Dai un'occhiata a Reflector.

+1

sarebbe utile avere un esempio dell'espressione lambda statica che crea il costruttore come parte della risposta. – Jim

3

Il primo post del blog si parla mostra un uso valido (come classe repository statico per un'implementazione ActiveRecord):

public static class Repository<T> 
{ 
    public static T[] FindAll { } 

    public static T GetById(int id){ } 

    public static void Save(T item) { } 
} 

O se si voleva implementare diversi metodi di ordinamento per le matrici generiche:

public static class ArraySort<T> 
{ 
    public static T[] BubbleSort(T[] source) { } 

    public static T[] QuickSort(T[] source) { } 
} 
+2

che sto cercando di più per un orientamento generale su quando sono benefiche . Com'è il tuo # 2 migliore di una classe statica non generica con metodi generici come: - public static T [] BubbleSort (sorgente T [])? –

+0

@Hightechrider la classe staic ti permetterebbe di avere risorse statiche private condivise che corrispondono al tipo di T se necessario. Altrimenti, ti fa solo risparmiare alcune battute. –

1

Hai ragione: non sono molto utili. Forse ci sono alcuni casi rari che sono eccezioni, però. Ad esempio, cosa succede se la classe era un repository specifico del tipo, come nell'esempio di Justin, ma manteneva una collezione statica come cache? In generale, se contiene uno stato, non solo metodi, potrebbe esserci un punto in questo.

+0

Grazie, +1.Finora abbiamo: usali (A) se ha stato per-derivato-tipo, o (B) se vuoi vincoli sui tipi generici e vuoi meno da digitare. È così? –

+0

Bene, puoi effettivamente aggiungere vincoli su una base per metodo quando la classe stessa non è generica, quindi anche questo non è il problema. Oltre a questo, penso che quello che hai sia fondamentalmente giusto. –

5

Un uso di classi statiche generiche che ho appreso di recente era possibile definire un vincolo a livello di classe per il tipo, quindi il vincolo si applica a tutti i membri statici della classe, ciò che ho anche appreso è che questo è non consentito per le classi generiche statiche che definiscono i metodi di estensione.

Ad esempio, se si intende creare una classe generica statica e si sa che tutti i tipi generici devono essere IComparable (solo un esempio), è possibile eseguire quanto segue.

static class MyClass<T> where T : IComparable 
{ 
    public static void DoSomething(T a, T b) 
    { 
    } 

    public static void DoSomethingElse(T a, T b) 
    { 
    } 
} 

Si noti che non è necessario applicare il vincolo a tutti i membri, ma solo a livello di classe.

8

I campi statici di un tipo generico sono specifici del tipo effettivo T. Ciò significa che è possibile memorizzare una cache di tipo specifico internamente. Questo potrebbe essere un motivo per creare un tipo generico statico.Ecco un esempio (piuttosto inutile, ma informativo):

public static TypeFactory<T> where T : new() 
{ 
    // There is one slot per T. 
    private static readonly object instance = new T(); 

    public static object GetInstance() { 
     return instance; 
    } 
} 

string a = (string)TypeFactory<string>.GetInstance(); 
int b = (int)TypeFactory<int>.GetInstance(); 
+0

Molto bello. l'uso di una fabbrica statica generica ha eliminato la necessità di fondere con 'T'. –

0

Una classe statica generica è esattamente utile quanto una determinata classe statica. La differenza è che non è necessario utilizzare il copia e incolla per creare una versione della classe statica per ciascun tipo su cui si desidera che funzioni. Rendi generica la classe e puoi "generare" una versione per ciascun set di parametri di tipo.

+0

Ma i metodi generici in una classe statica offrono quasi lo stesso vantaggio (nessun taglio e incolla necessari per lavorare con qualsiasi T), la differenza sembra essere che A) la classe statica generica può avere uno stato che è 'per-derivato-tipo 'al contrario di un solo stato per tutte le classi derivate e B) è meno la digitazione a volte per definire T e i suoi vincoli una volta in cima alla classe. È così? –

+0

Hai praticamente capito bene, ma stai sottovalutando la convenienza. Ho appena svolto un importante sforzo di refactoring che richiedeva un uso massiccio di classi, metodi, interfacce e delegati generici. C'erano alcune situazioni in cui dovevi usare lo stesso parametro tipo, per "passare il tipo" da dove è stato istanziato fino in fondo. In caso contrario, i tipi non corrispondono: il tipo T in un metodo potrebbe non essere uguale al tipo T in un altro. –

+0

Ma nessuna di queste classi * statiche * generiche? Le classi chiaramente non generiche * non-static * sono incredibilmente utili perché condividete le informazioni digitate tra i metodi, ma in una classe statica generica l'unico stato che è possibile condividere è statico e ciò sembra significare che ci sono solo scenari molto limitati in cui mai essere preferibile a una classe non statica generica (istanziata come un singleton se necessario) o una classe statica non generica con metodi statici generici. –

2

Quali sono gli usi chiave di una classe generica statica in C#? Quando dovrebbero essere usati ? Quali esempi illustrano al meglio il loro uso ?

Penso che in generale, si dovrebbe evitare di creare parametri di tipo su classi statiche, altrimenti non si può dipendere dall'inferenza di tipo per rendere più conciso il codice client.

Per fare un esempio concreto, supponiamo che stiate scrivendo una classe di utilità statica per gestire le operazioni sugli elenchi. Puoi scrivere la classe in due modi:

// static class with generics 
public static class ListOps<T> 
{ 
    public static List<T> Filter(List<T> list, Func<T, bool> predicate) { ... } 
    public static List<U> Map<U>(List<T> list, Func<T, U> convertor) { ... } 
    public static U Fold<U>(List<T> list, U seed, Func<T, U> aggregator) { ... } 
} 

// vanilla static class 
public static class ListOps 
{ 
    public static List<T> Filter<T>(List<T> list, Func<T, bool> predicate) { ... } 
    public static List<U> Map<T, U>(List<T> list, Func<T, U> convertor) { ... } 
    public static U Fold<T, U>(List<T> list, U seed, Func<T, U> aggregator) { ... } 
} 

Ma le classi sono equivalenti, ma quale è più facile da usare? Confronto:

// generic static class 
List<int> numbers = Enumerable.Range(0, 100).ToList(); 
List<int> evens = ListOps<int>.Filter(numbers, x => x % 2 = 0); 
List<string> numberString = ListOps<int>.Map(numbers, x => x.ToString()); 
int sumOfSquares = ListOps<int>.Fold(numbers, 0, (acc, x) => acc + x*x); 

// vanilla static class 
List<int> numbers = Enumerable.Range(0, 100).ToList(); 
List<int> evens = ListOps.Filter(numbers, x => x % 2 = 0); 
List<string> numberString = ListOps.Map(numbers, x => x.ToString()); 
int sumOfSquares = ListOps.Fold(numbers, 0, (acc, x) => acc + b * b); 

A mio parere, ListOps<someType> è ingombrante ed impacciato. La definizione della classe vanilla è leggermente più grande, ma il codice client è più facile da leggere, grazie all'inferenza di tipo.

Nel caso peggiore, C# non può inferire i tipi e devi specificarli a mano. Preferiresti scrivere ListOps<A>.Map<B>(...) o ListOps.Map<A, B>(...)? La mia preferenza è verso quest'ultimo.


La strategia di cui sopra funziona particolarmente bene quando la classe statica detiene nessuno stato mutabile, o il suo stato mutevole è noto al momento della compilazione .

Se la classe statica mantiene lo stato mutabile il cui tipo non è determinato in fase di compilazione, allora probabilmente avete un caso d'uso per una classe statica con params generici. Speriamo che queste occasioni siano poche e lontane tra loro, ma quando accadrà, sarai felice C# supporta la funzionalità.

+0

Grazie. Penso che l'argomento secondo cui quest'ultimo può anche essere trasformato in metodi di estensione è particolarmente rilevante per il tuo esempio! Un punto che non capisco è l'ultima sezione riguardante "tipo non determinato in fase di compilazione" - che sembra un uso per generici dinamici che * sono * determinati in fase di compilazione. Il problema non si riduce a "È necessario uno stato mutabile separato per ciascuna sottoclasse della classe statica generica o esiste uno solo stato mutabile memorizzato per tutte le derivate?"? –

10

Fare una classe static non aggiunge alcuna funzionalità - è solo un controllo utile se si intende utilizzare una classe senza istanziarla. E ci sono molti usi per questo ...

È possibile utilizzare le classi generiche statiche per aggirare una limitazione: C# non consente la specializzazione parziale. Ciò significa che devi specificare tutti i parametri del tipo o nessuno. Tuttavia, ciò può essere inutilmente prolisso.

Ad esempio:

static class Converter { 
    public TOut Convert<TIn, TOut>(TIn x) {...} 
} 

classe precedente non consente l'inferenza di tipo dal momento che l'inferenza non funziona su valori di ritorno. Tuttavia, non è possibile specificare il tipo di valore restituito senza specificare anche il tipo di input poiché non è possibile specializzarsi parzialmente. Utilizzo di una classe generica (possibilmente statica), è possibile specificare solo uno dei due tipi:

static class ConvertTo<TOut> { 
    public TOut Convert<TIn>(TIn x) {...} 
} 

In questo modo si può lasciare il lavoro inferenza di tipo sul tipo di parametro e specificare solo il tipo di ritorno.

(Anche se il caso precedente è concepibile, non richiede che la classe generica sia statica, ovviamente).


secondo luogo, (come Steven first pointed out) un campi statici separati esiste per ogni tipo e della costruzione che rende classi statici posti per memorizzare informazioni aggiuntive tipi o combinazioni di tipi. In sostanza, è un hash semi-statico che digita i tipi.

Una tabella di ricerca semi-statica che digita sui tipi sembra un po 'arcana, ma in realtà è una struttura molto, molto utile perché consente di archiviare riflessioni costose e risultati di generazione del codice in cui sono quasi liberi di cercare (più economico di un dizionario perché ottiene JIT-ed ed evita una chiamata a .GetType()). Se stai facendo metaprogramming, è fantastico!

Per esempio, io uso questo in ValueUtils per memorizzare le funzioni di hash generati:

//Hash any object: 
FieldwiseHasher.Hash(myCustomStructOrClass); 

//implementation: 
public static class FieldwiseHasher { 
    public static int Hash<T>(T val) { return FieldwiseHasher<T>.Instance(val); } 
} 

public static class FieldwiseHasher<T> { 
    public static readonly Func<T, int> Instance = CreateLambda().Compile(); 
    //... 
} 

metodi generici statici consentono tipo di inferenza per rendere l'utilizzo veramente facile; i campi statici su classi generiche consentono di archiviare virtualmente la memoria dei dati (meta). Non mi sorprenderebbe affatto se gli ORM come Dapper e PetaPoco usassero tecniche come questa; ma è anche ottimo per i (de) serializzatori. Una limitazione è che si ottiene un sovraccarico basso perché si sta vincolando al tipo compile-time; se l'oggetto che viene passato è in realtà un'istanza di una sottoclasse, probabilmente stai vincolando al tipo sbagliato - e l'aggiunta di controlli per evitare che questo tipo mina il vantaggio di essere a basso overhead.

2

Io li uso per simulare un DbSet durante il test contro le classi che utilizzano i metodi Async di EntityFramework.

public static class DatabaseMockSetProvider<TEntity> where TEntity: class 
{ 
    public static DbSet<TEntity> CreateMockedDbSet(IQueryable<TEntity> mockData) 
    { 
     var mockSet = Mock.Create<DbSet<TEntity>>(); 
     Mock.Arrange(() => ((IDbAsyncEnumerable<TEntity>)mockSet).GetAsyncEnumerator()) 
      .Returns(new TestDbAsyncEnumerator<TEntity>(mockData.GetEnumerator())); 
     Mock.Arrange(() => ((IQueryable<TEntity>)mockSet).Provider) 
      .Returns(new TestDbAsyncQueryProvider<TEntity>(mockData.Provider)); 
     Mock.Arrange(() => ((IQueryable<TEntity>)mockSet).Expression).Returns(mockData.Expression); 
     Mock.Arrange(() => ((IQueryable<TEntity>)mockSet).ElementType).Returns(mockData.ElementType); 
     Mock.Arrange(() => ((IQueryable<TEntity>)mockSet).GetEnumerator()).Returns(mockData.GetEnumerator()); 

     return mockSet; 
    } 
} 

e usarli in questo modo nel mio test di unità - consente di risparmiare un sacco di tempo e li può usare per qualsiasi tipi di entità:

var mockSet = DatabaseMockSetProvider<RecipeEntity>.CreateMockedDbSet(recipes); 
     Mock.Arrange(() => context.RecipeEntities).ReturnsCollection(mockSet); 
Problemi correlati