2010-04-21 24 views
21

¿Cuáles son los usos clave de un Estático Clase genérica en C#? ¿Cuándo deberían ser utilizados? ¿Qué ejemplos ilustran mejor su uso?¿Usos para clases genéricas estáticas?

p. Ej.

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

Dado que no se pueden definir los métodos de extensión en los que parecen ser un tanto limitado en su utilidad. Las referencias web sobre el tema son escasas, por lo que no hay mucha gente que las use. Aquí hay un par: -

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

Static Generic Class as Dictionary


Resumen de las respuestas dadas

Las cuestiones clave parecen ser "¿Cuál es la diferencia entre una clase genérica estática con estática métodos y clase estática no genérica con miembros genéricos estáticos? "

La decisión sobre qué usar parece girar en torno a "¿La clase necesita almacenar el estado específico del tipo internamente?"

Si no hay necesidad de almacenamiento interno específico del tipo, parece preferible una clase estática no genérica con métodos estáticos genéricos porque la sintaxis de llamada es más agradable y puede definir métodos de extensión dentro de ella.

Respuesta

18

Uso las clases genéricas estáticas para almacenar en caché el código de reflejo pesado.

Digamos que necesito construir un árbol de expresiones que ejemplifique objetos. Lo compilo una vez en el constructor estático de la clase, lo compilo en una expresión lambda y luego lo guardo en caché en un miembro de la clase estática. A menudo no hago que estas clases sean evaluables públicamente, generalmente son ayudantes para otras clases. Al almacenar en caché mis expresiones de esta manera, evito la necesidad de almacenar en caché mis expresiones en algún tipo de Dictionary<Type, delegate>.

Hay un ejemplo de este patrón en el BCL. Los métodos de extensión (DataRow) Field<T>() y SetField<T>() usan la clase genérica estática (privada) System.Data.DataRowExtensions+UnboxT<T>. Compruébalo con Reflector.

+1

sería informativo tener una muestra del constructor de creación de expresiones lambda estáticas como parte de la respuesta. – Jim

3

La primera entrada del blog mencionas muestra un uso válido (como una clase repositorio estático para una implementación ActiveRecord):

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

    public static T GetById(int id){ } 

    public static void Save(T item) { } 
} 

O si desea aplicar diferentes métodos de clasificación para matrices genéricas:

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

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

que estoy buscando más de una orientación general sobre cuando son beneficiosas . ¿Cómo es su número 2 mejor que una clase estática no genérica con métodos genéricos en ella como: - público T estático [] BubbleSort (fuente T [])? –

+0

@Hightechrider la clase staic le permitiría tener recursos privados estáticos compartidos que coincidan con el tipo de T si lo necesita. De lo contrario, solo te ahorra algunas teclas. –

1

Tienes razón: no son de mucha utilidad. Sin embargo, tal vez haya algunos casos excepcionales que sean excepciones. Por ejemplo, ¿qué pasa si la clase es un repositorio específico del tipo, como en el ejemplo de Justin, pero mantiene una colección estática como un caché? En general, si contiene estados, no solo métodos, puede haber un punto en esto.

+0

Gracias, +1.Hasta ahora tenemos: Úselos (A) Si tiene estado por tipo derivado, o (B) si quiere restricciones en el/los Tipo (s) genérico (s) y quiere menos para escribir. ¿Es asi? –

+0

Bueno, en realidad puede agregar restricciones por método cuando la clase en sí no es genérica, por lo que ese tampoco es el problema. Aparte de eso, creo que lo que tienes es básicamente correcto. –

5

Un uso de clases genéricas estáticas que aprendí recientemente fue posible es definir una restricción en el nivel de clase para el tipo, luego la restricción se aplica a todos los miembros estáticos de la clase, lo que también aprendí es que esto es no permitido para clases genéricas estáticas que definen métodos de extensión.

Por ejemplo, si vas a crear una clase genérica estática y sabes que todos los tipos genéricos deben ser IComparable (solo un ejemplo), entonces puedes hacer lo siguiente.

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

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

Observe que no tuve que aplicar la restricción a todos los miembros, sino solo a nivel de clase.

8

Los campos estáticos de un tipo genérico son específicos del tipo T. Esto significa que puede almacenar un caché específico de tipo internamente. Esta podría ser una razón para crear un tipo genérico estático.Aquí está un ejemplo (bastante inútil, pero 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

Muy agradable. usar una fábrica genérica estática eliminó la necesidad de lanzar a 'T'. –

0

Una clase genérica estática es exactamente tan útil como cualquier clase estática dada. La diferencia es que no tiene que usar copiar y pegar para crear una versión de la clase estática para cada tipo en que desee que funcione. Haces que la clase sea genérica y puedes "generar" una versión para cada conjunto de parámetros de tipo.

+0

Pero los métodos genéricos en una clase estática proporcionan casi el mismo beneficio (no es necesario cortar y pegar allí para trabajar con ninguna T), la diferencia parece ser que A) la clase estática genérica puede tener un estado que es 'por-tipo derivado 'a diferencia de un solo estado para todas las clases derivadas y B) a veces es menos tipeado para definir T y sus restricciones una vez en la parte superior de la clase. ¿Es asi? –

+0

Has acertado, pero estás subestimando la comodidad. Acabo de pasar por un gran esfuerzo de refactorización que requirió un uso intensivo de clases genéricas, métodos, interfaces y delegados. Hubo algunas situaciones en las que _deberías_ usar el mismo parámetro de tipo, para "pasar el tipo" desde donde se instanciaron hasta el final. De lo contrario, los tipos no coinciden: el tipo T en un método podría no ser el mismo que el tipo T en otro. –

+0

¿Pero alguna de ellas era clases genéricas * estáticas *? Claramente las clases genéricas * no estáticas * son tremendamente útiles porque usted comparte información tipada entre métodos, pero en una clase estática genérica, el único estado que puede compartir es el estado estático y eso parece significar que solo hay escenarios muy limitados en los que Alguna vez será preferible a una clase genérica no estática (instanciada como singleton si es necesario) o una clase estática no genérica con métodos estáticos genéricos. –

2

¿Cuáles son los usos clave de un Static Clase genérica en C#? ¿Cuándo deberían ellos ser utilizados? ¿Qué ejemplos ilustran mejor el uso de ?

Creo que, en general, debe evitar crear parámetros de tipo en las clases estáticas, de lo contrario no puede depender de la inferencia de tipos para hacer que su código de cliente sea más conciso.

Para dar un ejemplo concreto, digamos que está escribiendo una clase de utilidad estática para manejar las operaciones en las listas. Puede escribir la clase de dos maneras:

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

Pero las clases son equivalentes, pero ¿cuál es más fácil de usar? Compare:

// 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); 

En mi opinión, ListOps<someType> es voluminoso y torpe. La definición de la clase vainilla es un poco más grande, pero el código del cliente es más fácil de leer, gracias a la inferencia de tipo.

En el peor de los casos, C# no puede inferir los tipos, y debe especificarlos a mano. ¿Prefieres escribir ListOps<A>.Map<B>(...) o ListOps.Map<A, B>(...)? Mi preferencia es hacia este último.


La estrategia anterior funciona particularmente bien cuando su clase estática mantiene ningún estado mutable, o su estado mutable es conocido en tiempo de compilación .

Si la clase estática tiene un estado mutable cuyo tipo no se determina en tiempo de compilación, entonces probablemente tenga un caso de uso para una clase estática con params genéricos. Afortunadamente, estas ocasiones son pocas y distantes, pero cuando sucedan, estarás feliz de que C# sea compatible con la funcionalidad.

+0

Gracias. ¡Creo que el argumento de que este último también puede convertirse en métodos de extensión es particularmente relevante para su ejemplo! Un punto que no entiendo es la última sección con respecto a "tipo no determinado en tiempo de compilación", que suena como un uso para dinámicos no genéricos que * están * determinados en tiempo de compilación. ¿No se reduce la cuestión a "¿Se requiere un estado mutable independiente para cada subclase de la clase estática genérica o hay solo un estado mutable almacenado para todas las derivadas?"? –

10

Hacer una clase static no agrega ninguna funcionalidad; es solo un control conveniente si tiene la intención de utilizar una clase sin instanciarla. Y hay varios usos para eso ...

Puede usar clases genéricas estáticas para evitar una limitación: C# no permite la especialización parcial. Eso significa que debe especificar todos los parámetros de tipo o ninguno. Sin embargo, eso puede ser innecesariamente detallado.

Por ejemplo:

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

la clase anterior no permite la inferencia de tipos ya que la inferencia no funciona en los valores de retorno. Sin embargo, tampoco puede especificar el tipo de valor devuelto sin especificar también el tipo de entrada, ya que no puede especializarse parcialmente. El uso de una clase genérica (posiblemente estática), puede especificar sólo uno de los dos tipos:

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

De esa manera usted puede dejar que el trabajo de la inferencia del tipo de parámetros y especificar sólo el tipo de retorno.

(Aunque el caso anterior es concebible, no requiere que la clase genérica sea estática, por supuesto).


En segundo lugar, (como Steven first pointed out) Existe una campos estáticos separados para cada tipo construido, y eso hace que las clases estáticas grandes lugares para almacenar información adicional sobre tipos o tipo de combinaciones. En esencia, es una tabla hash semi-estática que teclea los tipos.

Una tabla de búsqueda semi-estática que teclee en los tipos suena un poco arcana, pero en realidad es una estructura muy, muy útil porque te permite almacenar costosos resultados de reflexión y generación de código donde son casi libres de mirar hacia arriba (más barato que un diccionario porque recibe JIT-ed y evita una llamada al .GetType()). Si estás haciendo metaprogramación, ¡esto es genial!

Por ejemplo, yo uso esto en ValueUtils para almacenar funciones hash generados:

//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(); 
    //... 
} 

métodos genéricos estáticas permiten tipo de inferencia para hacer uso de un proceso más sencillo; los campos estáticos en clases genéricas permiten prácticamente el almacenamiento libre de sobrecarga de (meta) datos. No me sorprendería en absoluto si los ORM como Dapper y PetaPoco usan técnicas como esta; pero también es ideal para (de) serializadores. Una limitación es que obtiene la baja sobrecarga porque está vinculando al tipo en tiempo de compilación; Si el objeto que se pasa es en realidad una instancia de una subclase, es probable que se esté vinculando al tipo incorrecto, y la adición de controles para evitar ese tipo de acción socava el beneficio de tener una sobrecarga baja.

2

Los uso para simular un DbSet cuando se prueban contra clases que usan métodos EntityFramework Async.

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

y utilizarlos como tal en mis pruebas unitarias - ahorra mucho tiempo y puede utilizarlas en todos los tipos de entidades:

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