usando una tipos tipo ser referencia parametrizada instanciar genéricos genericos genericas debe cuando colecciones clases clase c# generics static

c# - una - tipos genéricos java



Usos para clases genéricas estáticas? (9)

¿Cuáles son los usos clave de una clase genérica estática en C #? ¿Cuándo deben usarse? ¿Qué ejemplos ilustran mejor su uso?

p.ej

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

Ya que no puede definir métodos de extensión en ellos, parecen ser algo limitados en su utilidad. Las referencias web sobre el tema son escasas, así que claramente no hay mucha gente que las use. Aquí hay una pareja: -

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

Clase genérica estática como diccionario

Resumen de las respuestas dadas

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

La decisión sobre cuál usar parece girar en torno a "¿Necesita la clase almacenar internamente un estado específico del tipo?"

Si no hay necesidad de almacenamiento interno específico del tipo, entonces es preferible una clase no genérica estática 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 él.


¿Cuáles son los usos clave de una clase genérica estática en C #? ¿Cuándo deben usarse? ¿Qué ejemplos ilustran mejor su uso?

Creo que, en general, debe evitar crear parámetros de tipo en clases estáticas, de lo contrario no puede depender de la inferencia de tipo para hacer que el código de su 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. Podrías 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? Comparar:

// 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, el ListOps<someType> es voluminoso y torpe. La definición de la clase de 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 tipos.

En el peor de los casos, C # no puede inferir los tipos, y usted tiene que 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 no tiene un estado mutable , o su estado mutable se conoce en tiempo de compilación .

Si la clase estática tiene un estado mutable cuyo tipo no está determinado en el momento de la compilación, es probable que tenga un caso de uso para una clase estática con parámetros genéricos. Esperemos que estas ocasiones sean pocas y distantes, pero cuando suceda, estarás contento de que C # sea compatible con la funcionalidad.


Hacer una clase static no agrega ninguna funcionalidad, es solo una comprobación conveniente si tiene la intención de usar una clase sin crear instancias. Y hay varios usos para eso ...

Puede usar clases genéricas estáticas para evitar una limitación: C # no permite una 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 de retorno sin especificar también el tipo de entrada, ya que no puede especializarse parcialmente. Usando una clase genérica (posiblemente estática), puede especificar solo uno de los dos tipos:

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

De esa manera puede dejar que la inferencia de tipos funcione en el tipo de parámetro y especificar solo 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 señaló Steven por primera vez ), existen campos estáticos separados para cada tipo construido, y eso hace que las clases estáticas sean lugares excelentes para almacenar información adicional sobre tipos o combinaciones de tipos. En esencia, es una tabla hash semi-estática que las claves de los tipos.

Una tabla de búsqueda semi-estática que teclea sobre tipos suena un poco pequeña, pero en realidad es una estructura muy, muy útil porque le permite almacenar costosos resultados de reflexión y generación de código donde son casi gratuitos para buscar (más barato que un diccionario porque recibe JIT-ed y evita una llamada a .GetType() ). Si estás haciendo metaprogramación, ¡esto es genial!

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

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

Los métodos genéricos estáticos permiten la inferencia de tipos para hacer que el uso sea realmente fácil; los campos estáticos en las clases genéricas permiten el almacenamiento de datos (meta) virtualmente sin gastos generales. No me sorprendería en absoluto si ORM''s como Dapper y PetaPoco usen técnicas como esta; pero también es ideal para (des) serializadores. Una limitación es que está obteniendo una sobrecarga baja porque está vinculado al tipo de tiempo de compilación ; Si el objeto que se pasa es en realidad una instancia de una subclase, es probable que esté enlazando con el tipo incorrecto, y agregar cheques para evitar que disminuya la ventaja de ser de bajo costo.


La primera publicación de blog que menciona muestra un uso válido (como una clase de repositorio estático para una implementación de 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 implementar 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) { } }


Los campos estáticos de un tipo genérico son específicos del tipo real T. Esto significa que puede almacenar internamente un tipo específico de caché. Esto podría ser una razón para crear un tipo genérico estático. Aquí hay 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();


Los uso para simular un DbSet cuando se prueba con clases que usan los métodos Async de 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; } }

Y usarlos así en mis pruebas de unidad: ahorra mucho tiempo y puede usarlos para cualquier tipo de entidad:

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


Tienes razón: no son de mucha utilidad. Tal vez hay algunos casos raros que son excepciones, sin embargo. Por ejemplo, ¿qué pasaría si la clase fuera un repositorio específico del tipo, como en el ejemplo de Justin, pero conservara una colección estática como un caché? En general, si contiene estado, no solo métodos, puede haber un punto para esto.


Un uso de las clases genéricas estáticas que aprendí recientemente fue posible 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 no está permitido para la estática. Clases genéricas que definen métodos de extensión.

Por ejemplo, si va a crear una clase genérica estática y sabe que todos los tipos genéricos deberían ser incomparables (solo un ejemplo), puede 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) { } }

Tenga en cuenta que no necesitaba aplicar la restricción a todos los miembros, sino solo a nivel de clase.


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 el que desea que funcione. Usted crea la clase genérica y puede "generar" una versión para cada conjunto de parámetros de tipo.


Utilizo clases genéricas estáticas para el almacenamiento en caché de código pesado de reflexión.

Digamos que necesito construir un árbol de expresiones que instancia a los objetos. Lo construyo una vez en el constructor estático de la clase, lo compilo en una expresión lambda, luego lo guardo en un miembro de la clase estática. A menudo no hago estas clases públicamente evaluables, por lo general son ayudantes para otras clases. Al almacenar en caché mis expresiones de esta manera, evito la necesidad de guardar 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>() utilizan la clase genérica estática (privada) System.Data.DataRowExtensions+UnboxT<T> . Compruébalo con Reflector.