2010-05-31 7 views
20

Quiero declarar un diccionario que almacena mecanografiadas IEnumerable 's de un tipo específico, con ese tipo exacto como la clave, así: (Editado para seguir comentario johny de g)En C#: ¿Cómo declarar un diccionario genérico con un tipo como clave y un IEnumerable <> de ese tipo como valor?

private IDictionary<Type, IEnumerable<T>> _dataOfType where T: BaseClass; //does not compile! 

Las clases concretas que quiero tienda, todos derivan de BaseClass, por lo tanto, la idea de usarlo como restricción. El compilador se queja de que espera un punto y coma después del nombre del miembro.

Si iba a funcionar, yo esperaría que esto haría que la posterior recuperación de la simple diccionario como:

IEnumerable<ConcreteData> concreteData; 
_sitesOfType.TryGetValue(typeof(ConcreteType), out concreteData); 

¿Cómo definir un diccionario de este tipo?

+3

restricción común de tipo de lado, declarando 'IDictionary >' constriñe claves para haber casos ** ** de 't', no' typeof (T) '. ¿Quieres indexar tus listas por 'typeof (T)' o por instancias de 'T'? si es anterior, debes declarar como 'IDictionary 'y lanzar miembros individuales del enumerable al consumir –

+0

Sí, johnny g, tienes razón, quiero usar el tipo, no una instancia. Edito eso. – Marcel

Respuesta

15

Es posible que ni siquiera necesita un diccionario para ser capaz de hacer esto - pero eso depende de sus necesidades. Si sólo necesita siempre 1 dicha lista según el tipo por dominio de aplicación (es decir, el "diccionario" es static), el siguiente patrón puede ser eficiente y promueve el tipo de inferencia muy bien:

interface IBase {} 

static class Container { 
    static class PerType<T> where T : IBase { 
     public static IEnumerable<T> list; 
    } 

    public static IEnumerable<T> Get<T>() where T : IBase 
     => PerType<T>.list; 

    public static void Set<T>(IEnumerable<T> newlist) where T : IBase 
     => PerType<T>.list = newlist; 

    public static IEnumerable<T> GetByExample<T>(T ignoredExample) where T : IBase 
     => Get<T>(); 
} 

Tenga en cuenta que usted debe pensar cuidadosamente antes de adoptar este enfoque acerca de la distinción entre el tipo de tiempo de compilación y el tipo de tiempo de ejecución. Este método le permitirá almacenar una variable IEnumerable<SomeType> con torsión en tiempo de ejecución bajo SomeType y -si la envía- bajo cualquiera de los tipos de base SomeType, incluido IBase, sin errores de tiempo de ejecución ni de compilación - que podría ser una función, o un error esperando a suceder, por lo que es posible que desee un if para verificarlo.

Además, este enfoque ignora el enhebrado; por lo tanto, si desea acceder a esta estructura de datos desde múltiples hilos, probablemente desee agregar algún bloqueo. Las lecturas/escrituras de referencia son atómicas, por lo que no se dañará si no se bloquea, pero los datos obsoletos y las condiciones de carrera son ciertamente posibles.

+1

+1 para la primera observación. Tiene razón acerca de la restricción de la clave. – Marcel

+0

Gracias por el código. Esto se adapta muy bien a mi caso. Lo uso ahora. – Marcel

11

No restringe T en el miembro privado; lo constriñes en el nivel de clase.

class Foo<T> where T : BaseClass 
{ 
    private IDictionary<T, IEnumerable<T>> _dataOfType; 

    public Foo(IDictionary<T, IEnumerable<T>> dataOfType) 
    { 
     this._dataOfType = dataOfType; 
    } 
} 
+5

Eso es correcto, y probablemente la razón por la que no compila. Sin embargo, esto no es lo que pretendo, me gustaría tener un miembro de tipo restringido, pero no una restricción de tipo en la clase. – Marcel

4

hacer una clase diccionario personalizado:

public class BaseClassDictionary<T, IEnumerable<T>> : Dictionary<T, IEnumerable<T>> 
    where T : BaseClass 
{ 
} 

continuación, puede utilizar este diccionario especializado en cambio, como tipo de campo:

private BaseClassDictionary<BaseClassDerivedType, IEnumerable<BaseClassDerivedType>> myDictionary; 
+0

+1, pero ¿no debería ser el Diccionario >? – Lucas

+0

@Lucas No veo lo que quiere decir –

+1

@Marcel, Respondiendo aquí, pero esto también se aplica a mi respuesta a continuación ... Esto NO le dará varios diccionarios. según lo que especifique, proporcionará UN diccionario, cuya colección de claves es una colección de Ts, y cuya colección Valores es una colección de cualquier objeto que implememt IEnumerable of T, es decir (que podría ser una matriz de Ts, una Lista de Ts, una lista enlazada de Ts, etc. o cualquier combinación de estos elementos ...) Esto aún puede no ser lo que quieres, pero es lo que dijiste ... –

7
  1. no puede limitarse una variable específica. Solo funciona en clases y métodos. Realmente no tiene ningún sentido en el nivel variable, para ser honesto.
  2. Lo que quiere es una clase personalizada - class WeirdDictionary : IDictionary<Type, IEnumerable>, que sobrecargará el método Agregar para tomar un tipo y un IEnumerable de ese tipo, que puede hacer usando restricciones, y convertir el IEnumerable <> a IEnumerable. Sobrecargue también el indexador y vuelva a colocarlo en el tipo correspondiente. Se necesita
    Todo este casting, ya que los genéricos son estrictos con IEnumerable<Base> ser tan bueno como IEnumerable<Derived> (Esto se llama varianza, creo?)

Esta solución es ligeramente generalizada, ya que las rocas de reutilización

Editar por 280Z28:

Al principio marqué esto porque el punto 2 era confuso y lo malinterpreté. Al utilizar la implementación explícita de métodos en IDictionary<Type, IEnumerable> y proporcionar alternativas genéricas, puede obtener una interfaz bastante limpia. Tenga en cuenta que no puede crear indizadores genéricos, por lo que siempre deberá usar TryGet<T> (que de todos modos es una buena idea). Solo incluí la implementación explícita de uno de los métodos IDictionary<> para mostrar cómo realizar las comprobaciones. No obtenga WeirdDictionary directamente de Dictionary<Type, IEnumerable> o perderá la capacidad de garantizar restricciones en los datos subyacentes.

class WeirdDictionary : IDictionary<Type, IEnumerable> 
{ 
    private readonly Dictionary<Type, IEnumerable> _data = 
     new Dictionary<Type, IEnumerable>(); 

    public void Add<T>(IEnumerable<T> value) 
    { 
     _data.Add(typeof(T), value); 
    } 

    public bool TryGet<T>(out IEnumerable<T> value) 
    { 
     IEnumerable enumerable; 
     if (_data.TryGetValue(typeof(T), out enumerable) 
     { 
      value = (IEnumerable<T>)enumerable; 
      return true; 
     } 

     value = null; 
     return false; 
    } 

    // use explicit implementation to discourage use of this method since 
    // the manual type checking is much slower that the generic version above 
    void IDictionary<Type, IEnumerable>.Add(Type key, IEnumerable value) 
    { 
     if (key == null) 
      throw new ArgumentNullException("key"); 
     if (value != null && !typeof(IEnumerable<>).MakeGenericType(key).IsAssignableFrom(value.GetType())) 
      throw new ArgumentException(string.Format("'value' does not implement IEnumerable<{0}>", key)); 

     _data.Add(key, value); 
    } 
} 

Fin 280Z28

+0

Respuesta bien elaborada. Sin embargo, junto con otras buenas respuestas, esto resulta ser más complicado de lo que pensaba. Como actualmente solo tengo 2 clases derivadas, ahora tiendo a administrar dos enumerables separados directamente como propiedades en mi clase. :-( – Marcel

+2

Hice algunas pruebas en la implementación 280Z28 siempre, funciona y funciona muy bien, en todas las clases. Esta es una idea clara, extraña. – Rubys

0

Prueba esto:

public class MyCustomDictionary<T>: Dictionary<T, IEnumerable<T>> { } 
+0

No es lo que realmente necesito, vea también mi comentario a johnny g – Marcel

+0

@ Marcel, comentado anteriormente bajo la respuesta de Enrico –

21

Use System.ComponentModel.Design.ServiceContainer que ya está disponible en .Net framework.

 ServiceContainer container = new ServiceContainer(); 

     IList<int> integers = new List<int>(); 
     IList<string> strings = new List<string>(); 
     IList<double> doubles = new List<double>(); 

     container.AddService(typeof(IEnumerable<int>), integers); 
     container.AddService(typeof(IEnumerable<string>), strings); 
     container.AddService(typeof(IEnumerable<double>), doubles); 
+1

Eso está bien.Sin embargo, ahora ya he implementado la solución provista por Eamon Nerbonne – Marcel

+2

Esta es la respuesta correcta, utilizando lo que ya está disponible en el marco. – Arjang

Cuestiones relacionadas