2010-07-03 7 views
9

Estoy escribiendo un bijective dictionary class, pero quiero asegurarme de que los dos tipos genéricos no son del mismo tipo por dos razones.Uso de dónde especificar diferentes genéricos

En primer lugar, me gustaría que implementa la interfaz IDictionary en ambas direcciones, pero

public class BijectiveDictionary<TKey, TValue> 
    : IDictionary<TKey, TValue>, IDictionary<TValue, TKey> 

me da " 'BijectiveDictionary < TKey, TValue >' no se puede aplicar tanto 'IDictionary < TKey, TValue >' y 'IDictionary < TValue, TKey >', ya que pueden unificar para algunas sustituciones de parámetros de tipo "(que es comprensible, pero indeseable.)

segundo lugar, me gustaría escribir un opt solución imitada si ambos tipos son iguales.

public class BijectiveDictionary<TKey, TValue> 
    : IDictionary<TKey, TValue> where TValue : TKey 
{ 
    // Optimized solution 
} 

public class BijectiveDictionary<TKey, TValue> 
    : IDictionary<TKey, TValue>, IDictionary<TValue, TKey> where TValue : !TKey 
{ 
    // Standard solution 
} 

¿Esto es posible?

Si no, puedo considerar la no implementación de IDictionary, pero no puedo garantizar que TValue this[TKey key] y TKey this[TValue key] sean diferentes, lo que sería desafortunado.


Parece que el problema aquí es que cuando los dos tipos son iguales, surgen los casos especiales.

Mi intención original era crear un diccionario que asigna exactamente una clave a exactamente un valor, y viceversa, de modo que para cada KeyValuePair<TKey, TValue>(X, Y), existe también un KeyValuePair<TValue, TKey>(Y, X).

Cuando TKey = TValue, entonces esto se puede simplificar a un solo diccionario:

public T this[T key] 
{ 
    get { return this[key]; } 
    set 
    { 
     base.Add(key, value); 
     base.Add(value, key); 
    } 
} 

En este caso, no se puede Add(2,3); Add(3,4) porque Add(2,3) mapas 3 a 2 también, y [3] volvería 2.

Sin embargo, Jaroslav Jandek's solution propuso utilizar un segundo diccionario para hacer esto para los casos cuando TKey! = TValue. Y aunque esto funciona maravillosamente para esos casos, (y lo que decidí implementar, al final) no sigue mi intención original cuando TKey = TValue, al permitir Add(2,3); Add(3,4) asignar una sola clave 3 a dos valores (2 en una dirección, y 4 en la otra, aunque creo que estrictamente hablando sigue siendo una función biológica válida.

+0

¿Qué estás usando el diccionario para? ¿Necesita siquiera la parte de su diccionario? Dicho esto, el código en mi respuesta maneja todos sus casos (implementando IDictonary, proporcionando bijection y manejando casos donde TKey == TValue). –

+2

Si quiere bijection para sí mismo para 'TKey == TValue', podría usar' SelfBijectiveDictionary : BijectiveDictionary {...} 'y anular su método' Add' y verificar primero 'if (base.Contains (key) | | this.Reversed.Contains (value)) throw ... '. Las clases se deben nombrar de manera diferente de todos modos, porque uno es bijective 'X-> Y' y el otro' S-> S' (bijective a sí mismo). –

+0

Señalar la diferencia entre 'X-> Y' y' S-> S' ayuda mucho. ¡Lo siento por toda la confusion! Y sí, estoy haciendo dos diccionarios nombrados por separado para ellos. Gracias por tu ayuda. – dlras2

Respuesta

3

¿Qué tal esto (enfoque diferente):

public class BijectiveDictionary<TKey, TValue> : Dictionary<TKey, TValue> 
{ 
    public BijectiveDictionary<TValue, TKey> Reversed { get; protected set; } 

    public BijectiveDictionary() 
    { 
     this.Reversed = new BijectiveDictionary<TValue,TKey>(true); 
     this.Reversed.Reversed = this; 
    } 

    protected BijectiveDictionary(bool reversedWillBeSetFromTheCallingBiji) { } 

    protected void AddRaw(TKey key, TValue value) 
    { 
     base.Add(key, value); 
    } 

    // Just for demonstration - you should implement the IDictionary interface instead. 
    public new void Add(TKey key, TValue value) 
    { 
     base.Add(key, value); 
     this.Reversed.AddRaw(value, key); 
    } 

    public static explicit operator BijectiveDictionary<TValue, TKey>(BijectiveDictionary<TKey, TValue> biji) 
    { 
     return biji.Reversed; 
    } 
} 

y en el código:

BijectiveDictionary<int, bool> a = new BijectiveDictionary<int, bool>(); 

a.Add(5, true); 
a.Add(6, false); 

Console.WriteLine(a[5]);// => True 
Console.WriteLine(((BijectiveDictionary < bool, int>)a)[true]);// => 5 
//or 
Console.WriteLine(a.Reversed[true]);// => 5 
+1

Nota: el lanzamiento explícito 'Console.WriteLine (((BijectiveDictionary < int, int>) a) [verdadero]);' ** no funcionará ** cuando los tipos de conjuntos sean los mismos. Es por razones obvias, quería señalarlo de todos modos. –

2

En resumen, no. Al optimizarlo, una opción podría ser una estrategia basada en un inicializador estático, que elija una implementación concreta apropiada para el caso del mismo tipo, pero de lo contrario; es decir,

public class BijectiveDictionary<TKey, TValue> 
    : IDictionary<TKey, TValue> 
{ 
    static readonly ISomeStrategy strategy; 
    static BijectiveDictionary() { 
     if(typeof(TKey) == typeof(TValue)) { 
      strategy = ... 
     } else { 
      strategy = ... 
     } 
    } 
} 

pero es casi seguro que se encuentre utilizando MakeGenericType y reflexión para crear la instancia (pero una vez creado debe estar bien - y sólo hacerlo una vez).

-1

Incluso si pudiera, lo que sería la salida de este código?

var dict = new BijectiveDictionary<int, int>(); 

dict.Add(2,3); 
dict.Add(3,4); 

Console.WriteLine(dict[3]); // Do I get 2 or 4? 

Tal vez se puede reestructurar el código de otra forma que no sea ambigua?

+0

No podría hacer esto, porque esto rompería la naturaleza biyectiva del diccionario (a menos que no esté entendiendo bien la biyección, lo cual es posible). 'Dict.Add (3,4);' se equivocaría porque 3 ya está asignado a 2. – dlras2

+0

@ Vilx-: Exactamente, es por eso que utilicé otro enfoque. Con mi ejemplo, obtendría implícitamente 4 y explícitamente 2. –

+0

@Daniel Rasmussen: 3 no está mapeado a 2. 3 está mapeado a 4. Debe comprender que '3 del conjunto A' es diferente de' 3 del conjunto B '. Mi ejemplo específicamente diferencia entre los 2 conjuntos (detectables por un puntero de referencia). –

2

¡Hasta cierto punto, esto se puede hacer! Utilizo un método de diferenciación, en lugar de cualificador (es) que limita los tipos.

No se unifica, de hecho podría ser mejor que si lo hiciera porque puede separar las interfaces separadas.

Vea mi publicación aquí, con un ejemplo totalmente funcional en otro contexto. https://stackoverflow.com/a/12361409/471129

Básicamente, lo que se hace es añadir otro parámetro de tipo de IIndexer, de modo que se convierta en IIndexer <TKey, TValue, TDifferentiator>.

A continuación, cuando se utiliza dos veces, se pasa "primero" a la primera utilización, y "segundo" para el segundo uso

Por lo tanto, la prueba de clase se convierte en: class Test<TKey, TValue> : IIndexer<TKey, TValue, First>, IIndexer<TValue, TKey, Second>

Por lo tanto, se puede hacer new Test<int,int>()

en primer lugar, y segundo son triviales:

interface First { } 

interface Second { } 
+0

Parece hackish, pero funciona. +1 –

Cuestiones relacionadas