c# - ireadonlydictionary - ireadonlylist




Conversion d'un dictionnaire imbriqué en IReadOnlyDictionary (2)

J'essaye de donner une IReadOnly-références aux objets internes de collection. Cela fonctionne bien dans la plupart des cas, mais pas si je veux convertir un dictionnaire contenant une collection dans un IReadOnlyDictionary contenant un IReadOnlyCollection.

Voici un exemple de code:

    var list = new List<int>();
    IReadOnlyList<int> listReference = list; //works;

    var dictionary = new Dictionary<int, int>();
    IReadOnlyDictionary<int, int> dictionaryReference = dictionary; //works

    var nestedList = new List<List<int>>();
    IReadOnlyList<IReadOnlyList<int>> nestedReadOnlyListReference = nestedList; //works

    var nestedDictionary = new Dictionary<int, List<int>>();
    //IReadOnlyDictionary<int, IReadOnlyList<int>> nestedReadOnlyDictionaryReference = nestedDictionary; //does not work, can not implicitly convert

    //current workaround
    var nestedDictionaryReferenceHelper = new Dictionary<int, IReadOnlyList<int>>();
    foreach (var kvpNestedDictionary in nestedDictionary)
    {
        nestedDictionaryReferenceHelper.Add(kvpNestedDictionary.Key, (IReadOnlyList<int>)kvpNestedDictionary.Value);
    }
    IReadOnlyDictionary<int, IReadOnlyList<int>> nestedReadOnlyDictionaryReference = nestedDictionaryReferenceHelper; //works, but is only a reference to the internal List, not to the dictionary itself

La solution de contournement est assez moche car elle a besoin de mémoire supplémentaire et nécessite une mise à jour manuelle chaque fois que les valeurs de nestedDictionary changent.

Existe-t-il un moyen simple de convertir de tels dictionnaires imbriqués?


À mon avis, le fait est que vous manquez l'occasion d'introduire un nouveau type approprié avec sa propre dignité. Si vous utilisez Dictionary<int, List<int>> vous vous verrez avec un code comme celui-ci chaque fois que vous avez besoin d'insérer une valeur:

if (!_dictionary.ContainsKey(key)) {
    var list = new List<int>();
    list.Add(value);
    _dictionary.Add(key, list);
} else {
    _dictionary[key].Add(value);
}

Et pire encore avec un code comme celui-ci lorsque vous voulez rechercher une valeur:

_dictionary.ContainsKey(key) && _dictionary[key].Contains(value);

Et la variation de ces exemples. Le pire est que vous exposer ce détail d'implémentation à vos utilisateurs de classe. Si ce détail change, vous casserez tout le code. Que, par exemple, si vous souhaitez remplacer List<int> avec HashSet<int> ?

Comment ça devrait être?

_multimap.Add(key, value);

Avec une interface appropriée (ici, je montre juste quelques méthodes):

public interface IMultiMap<TKey, TValue> {
    void Add(TKey key, TValue value);
    bool ContainsKey(TKey key);
}

Et sa mise en œuvre:

public sealed class MultiMap<TKey, TValue> : IMultiMap<TKey, TValue> {
    // ...

    private Dictionary<int, List<int>> _items;
}

Vous pouvez introduire IReadOnlyMultiMap<TKey, TValue> :

public interface IReadOnlyMultiMap<TKey, TValue> {
    bool ContainsKey(TKey key);
}

IReadOnlyMultiMap<TKey, TValue> simplement IReadOnlyMultiMap<TKey, TValue> dans MultiMap<TKey, TValue> et pour retourner une collection en lecture seule vous n'avez rien à faire (exemple fictif):

IReadOnlyMultiMap<int, int> MakeReadOnly(MultiMap<int, int> map) {
    return map; // Nothing to do!
}

Notez que vous souhaiterez peut-être introduire un nouveau ReadOnlyMultiMap<TKey, TValue> pour percer les appels en lecture dans la collection live sous-jacente (pour éviter que les appelants ne transtypent en MultiMap<TKey, TValue> pour contourner la limitation en lecture seule). Preuve de concept:

public sealed class ReadOnlyMultiMap<TKey, TValue> : IReadOnlyMultiMap<TKey, TValue> {
    public ReadOnlyMultiMap(IMultiMap<TKey, TValue> collection) {
        _collection = collection;
    }

    public bool ContainsKey(TKey key) {
        return _collection.ContainsKey(key);
    }

    private readonly IMultiMap<TKey, TValue> _collection;
}

Pour retourner une vue en lecture seule, procédez comme suit:

IReadOnlyMultiMap<int, int> MakeReadOnly(MultiMap<int, int> map) {
    return new ReadOnlyMultiMap<int, int>(map);
}

Notez que j'ai parlé de détails de mise en œuvre . Vous êtes encore en train d'exposer un détail d'implémentation (vous utilisez un multimap) et si un tel code est pour une API publique, vous devez introduire un nouveau type (correctement nommé) pour décrire ce qu'il contient, pas comment le stockage est implémenté . Il peut s'agir de MeasureCollection , SoccerScoreCollection ou de tout ce dont parle votre modèle, le stockage peut varier mais pas le contenu .


Le problème pour la conversion qui échoue est le KeyValuePair: Bien que la classe Derived héritant de la classe Base, KeyValuePair n'est pas une sous-classe de KeyValuePair; voir les définitions ( Dictionary , IReadOnlyDictionary ).

Donc, vous aurez toujours besoin d'une solution de rechange (l'approche MultiMap me semble être une, aussi ...). Si nestedDictionary est privé, vous avez donc un contrôle complet sur votre classe, vous pourriez vous en sortir:

var nestedDictionary = new Dictionary<int, IReadOnlyList<int>>();
IReadOnlyDictionary<int, IReadOnlyList<int>> nestedReadOnlyDictionaryReference = nestedDictionary;

et chaque fois que vous modifiez une liste dans le dictionnaire en appliquant un cast à List<int> . Une autre solution de contournement laide, je l'admets, mais vous permet d'économiser la mémoire supplémentaire et la gestion de la redondance et conserve l'interface publique (supposée ...) de IReadOnlyDictionary<int, IReadOnlyList<int>> .

Edit: juste une idée, n'a pas testé, mais cela pourrait fonctionner: Avoir votre propre dictionnaire en ajoutant les interfaces manquantes pour être assignable au dictionnaire en lecture seule:

public class MyDictionary
    : Dictionary<int, List<int>>,
      ICollection<KeyValuePair<int, IReadOnlyList<int>>,
      IEnumerable<KeyValuePair<int, IReadOnlyList<int>>, 
      IReadOnlyCollection<KeyValuePair<int, IReadOnlyList<int>>
{
}

J'ai peut-être manqué une interface à mettre en place, et vous pourriez avoir à implémenter quelques membres pour le moment. Si cela fonctionne, peut-être la solution la plus propre ...





readonly