2010-07-03 5 views
9

J'écris un bijective dictionary class, mais je veux m'assurer que les deux types génériques ne sont pas du même type pour deux raisons.Utilisation Où spécifier différents génériques

Tout d'abord, je voudrais à mettre en œuvre l'interface IDictionary dans les deux sens, mais

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

me donne " 'BijectiveDictionary < TKey, TValue >' ne peut pas mettre en œuvre à la fois 'IDictionary < TKey, TValue >' et 'IDictionary < TValue, TKey >' parce qu'ils peuvent unifier pour certaines substitutions de paramètres de type "(ce qui est compréhensible, mais indésirable.)

Deuxièmement, je voudrais écrire un opt solution imitée si les deux types sont les mêmes.

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 
} 

Est-ce possible?

Sinon, je peux envisager de ne pas mettre en œuvre IDictionary, mais je ne pouvais pas garantir TValue this[TKey key] et TKey this[TValue key] serait différent, ce qui serait regrettable.


Il semble que le problème ici est que lorsque les deux types sont les mêmes, les cas spéciaux se présentent. Mon intention initiale était de créer un dictionnaire qui mappe exactement une clé à exactement une valeur, et inversement, de sorte que pour chaque KeyValuePair<TKey, TValue>(X, Y), un existe également.

Lorsque TKey = TValue, cela peut être simplifié vers le bas à un seul dictionnaire:

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

Dans ce cas, vous ne pouvez pas Add(2,3); Add(3,4) parce Add(2,3) cartes 3-2 aussi bien, et [3] retournerait 2.

Toutefois, Jaroslav Jandek's solution a proposé d'utiliser un deuxième dictionnaire pour ce faire dans les cas où TKey! = TValue. Et bien que cela fonctionne merveilleusement pour ces cas, (et ce que j'ai décidé de mettre en œuvre, à la fin) il ne suit pas tout à fait mon intention quand TKey = TValue, en permettant Add(2,3); Add(3,4) de mapper une seule clé 3 à deux valeurs (2 dans dans l'autre sens, et 4), même si je crois à proprement parler, c'est encore une fonction bijective valide.

+0

Qu'est-ce que vous utilisez le dictionnaire pour? Avez-vous besoin de la partie surjection de votre dictionnaire? Cela dit, le code dans ma réponse gère tous vos cas (implémentant IDictonary, fournissant des cas de bijection et de gestion où TKey == TValue). –

+2

Si vous voulez la bijection à TKey == TValue', vous pouvez utiliser 'SelfBijectiveDictionary : BijectiveDictionary {...}' et en remplaçant sa méthode 'Add' et en vérifiant d'abord' if (base.Contains (key) | | this.Reversed.Contains (value)) lancer ... '. Les classes devraient être nommées différemment de toute façon, parce que l'une est bijective 'X-> Y' et l'autre' S-> S' (bijective à elle-même). –

+0

Souligner la différence entre 'X-> Y' et' S-> S' aide beaucoup. Désolé pour le mal-entendu! Et oui - je fais deux dictionnaires nommés séparément pour eux. Merci de votre aide. – dlras2

Répondre

3

Que diriez-vous cette (approche différente):

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

et dans le code:

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

Note: la distribution explicite 'Console.WriteLine (((BijectiveDictionary < int, int>) a) [true]);' ** ne fonctionnera pas ** lorsque les types d'ensembles sont les mêmes. C'est pour des raisons évidentes - je voulais le signaler de toute façon. –

2

En bref, non. Pour l'optimiser, une option pourrait être une stratégie basée sur un initialiseur statique, qui choisit une implémentation concrète appropriée pour le cas de même type, mais sinon; à savoir

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

mais vous presque certainement vous trouver en utilisant MakeGenericType et de réflexion pour créer l'instance (mais une fois créé, il devrait être bien - et vous ne faites qu'une fois).

-1

Même si vous pouviez, quelle serait la sortie de ce code?

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

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

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

Peut-être pouvez-vous restructurer votre code d'une autre manière, ce qui n'est pas ambigu?

+0

Vous ne pourriez pas faire cela, parce que cela casserait la nature bijective du dictionnaire (à moins que je ne comprenne pas la bijection, ce qui est possible.) 'Dict.Add (3,4);' serait err parce que 3 est déjà mappé 2. – dlras2

+0

@ Vilx-: Exactement, c'est pourquoi j'ai utilisé une autre approche. Avec mon exemple, vous obtiendriez implicitement 4 et explicitement 2. –

+0

@Daniel Rasmussen: 3 n'est pas mappé à 2. 3 est mappé à 4. Vous devez comprendre que '3 de l'ensemble A' est différent de' 3 de l'ensemble B '. Mon exemple différencie spécifiquement entre les 2 ensembles (détectable par un pointeur de référence). –

2

Dans une certaine mesure, cela peut être fait! J'utilise une méthode de différenciation, au lieu de qualificatif (s) limitant les types.

Il ne s'unifie pas, en fait, il pourrait être mieux que si c'était le cas parce que vous pouvez taquiner les interfaces séparées.

Voir mon article ici, avec un exemple complet dans un autre contexte. https://stackoverflow.com/a/12361409/471129

Fondamentalement, ce que vous faites est d'ajouter un autre paramètre de type à IIndexer, de sorte qu'il devienne IIndexer <TKey, TValue, TDifferentiator>.

Ensuite, vous lorsque vous l'utilisez deux fois, vous passez « d'abord » à la 1ère utilisation, et « deuxième » pour la 2ème utilisation

Ainsi, Test de classe devient: class Test<TKey, TValue> : IIndexer<TKey, TValue, First>, IIndexer<TValue, TKey, Second>

Ainsi, vous pouvez faire new Test<int,int>()

où d'abord, et deuxième sont trivial:

interface First { } 

interface Second { } 
+0

Semble hackish, mais cela fonctionne. +1 –

Questions connexes