web-dev-qa-db-fra.com

Puis-je créer un dictionnaire de types génériques?

Je voudrais créer un objet Dictionary, avec une chaîne de clés, contenant des valeurs de type générique. J'imagine que cela ressemblerait à quelque chose comme ça:

Dictionary<string, List<T>> d = new Dictionary<string, List<T>>();  

Et permettez-moi d'ajouter ce qui suit:

d.Add("Numbers", new List<int>());
d.Add("Letters", new List<string>());

Je sais que je peux le faire pour une liste de chaînes, par exemple, en utilisant cette syntaxe:

Dictionary<string, List<string>> d = new Dictionary<string, List<string>>();
d.Add("Key", new List<string>());

mais je voudrais le faire pour une liste générique si possible ...

2 questions alors:

  1. C'est possible?
  2. Quelle est la syntaxe?

Merci beaucoup,
Jon

44
Jon Artus

EDIT: Maintenant, j'ai relu la question ...

Vous ne pouvez pas faire cela, mais une collection personnalisée le traiterait dans une certaine mesure. Vous auriez fondamentalement une méthode Add générique:

public void Add<T>(string key, List<T> list)

(La collection elle-même ne serait pas générique - sauf si vous souhaitez rendre le type de clé générique.)

Vous ne pouviez pas extraire} _ valeurs de cette manière fortement typée, car le compilateur ne saura pas quel type vous avez utilisé pour une clé particulière. Si vous faites de la clé le type lui-même, vous vous retrouvez avec une situation légèrement meilleure, mais qui n'est toujours pas prise en charge par les collections existantes. C'est la situation à laquelle ma réponse initiale répondait.

EDIT: Réponse originale, alors que je n'avais pas bien lu la question, mais qui peut être informative de toute façon ...

Non, vous ne pouvez pas faire en sorte qu'un argument de type dépende d'un autre, j'en ai bien peur. C'est l'une des choses que l'on pourrait vouloir exprimer dans un système de type générique mais que les contraintes de .NET ne permettent pas. Il y aura toujours de tels problèmes et les concepteurs .NET ont choisi de garder les génériques relativement simples.

Cependant, vous pouvez écrire une collection pour l'appliquer assez facilement. J'ai un exemple dans un article de blog qui ne conserve qu'une seule valeur, mais il serait facile d'étendre cela à une liste.

30
Jon Skeet

Est-ce que quelque chose aimerait ce travail?

public class GenericDictionary
{
    private Dictionary<string, object> _dict = new Dictionary<string, object>();

    public void Add<T>(string key, T value) where T : class
    {
        _dict.Add(key, value);
    }

    public T GetValue<T>(string key) where T : class
    {
        return _dict[key] as T;
    }
}

Fondamentalement, il couvre tous les acteurs dans les coulisses pour vous.

21
JoshBerke

Je préfère cette façon de mettre des types génériques dans une collection:

interface IList
{
  void Add (object item);
}

class MyList<T> : List<T>, IList
{
  public void Add (object item)
  {
    base.Add ((T) item); // could put a type check here
  }
}

class Program
{
  static void Main (string [] args)
  {
    SortedDictionary<int, IList>
      dict = new SortedDictionary<int, IList> ();

    dict [0] = new MyList<int> ();
    dict [1] = new MyList<float> ();

    dict [0].Add (42);
    dict [1].Add ("Hello"); // Fails! Type cast exception.
  }
}

Mais vous perdez les contrôles de type au moment de la compilation.

3
Skizz

Je n'ai pas trouvé ce que je cherchais ici, mais après lecture, je pense que c'est peut-être ce qui est demandé, donc une tentative de réponse.

Le problème est que lorsque vous utilisez Dictionary, il s'agit d'un type construit fermé et tous les éléments doivent être du type TValue. Je vois cette question à plusieurs endroits sans une bonne réponse.

Le fait est que je veux indexer mais chaque élément doit avoir un type différent et basé sur la valeur de TKey, nous connaissons déjà le type. N'essayez pas de contourner la boxe, mais simplement d'obtenir un accès plus élégant, comme DataSetExtensions Field. Et ne voulez pas utiliser dynamique parce que les types sont connus et que ce n’est tout simplement pas souhaitable.

Une solution peut être de créer un type non générique qui n'expose pas T au niveau de la classe et provoque donc la construction de la partie TValue du dictionnaire. Ensuite, saupoudrer dans une méthode fluide pour aider l'initialisation.

public class GenericObject
{
    private object value;

    public T GetValue<T>()
    {
        return (T)value;
    }

    public void SetValue<T>(T value)
    {
        this.value = value;
    }

    public GenericObject WithValue<T>(T value)
    {
        this.value = value;
        return this;
    }
}

class Program
{
    static void Main(string[] args)
    {
        Dictionary<string, GenericObject> dict = new Dictionary<string, GenericObject>();

        dict["mystring"] = new GenericObject().WithValue<string>("Hello World");
        dict["myint"] = new GenericObject().WithValue<int>(1);

        int i = dict["myint"].GetValue<int>();
        string s = dict["mystring"].GetValue<string>();
    }
}
2
Hank Hatch

Nous utilisons beaucoup de réflexion pour créer un outil d'administration extensible. Nous avions besoin d'un moyen d'enregistrer des éléments dans la recherche globale dans la définition du module. Chaque recherche renverrait des résultats de manière cohérente, mais chacune aurait des dépendances différentes. Voici un exemple d’enregistrement d’une recherche pour un seul module:

public void ConfigureSearch(ISearchConfiguration config)
    {
        config.AddGlobalSearchCallback<IEmploymentDataContext>((query, ctx) =>
        {
            return ctx.Positions.Where(p => p.Name.Contains(query)).ToList().Select(p =>
                new SearchResult("Positions", p.Name, p.ThumbnailUrl,
                    new UrlContext("edit", "position", new RouteValueDictionary(new { Id = p.Id }))
                    ));
        });
    }

En arrière-plan lors de l'inscription du module, nous parcourons chaque module et ajoutons le Func à une table de recherche avec une instance de

public class GenericFuncCollection : IEnumerable<Tuple<Type, Type, Object>>
{
    private List<Tuple<Type, Type, Object>> objects = new List<Tuple<Type, Type, Object>>();

    /// <summary>
    /// Stores a list of Func of T where T is unknown at compile time.
    /// </summary>
    /// <typeparam name="T1">Type of T</typeparam>
    /// <typeparam name="T2">Type of the Func</typeparam>
    /// <param name="func">Instance of the Func</param>
    public void Add<T1, T2>(Object func)
    {
        objects.Add(new Tuple<Type, Type, Object>(typeof(T1), typeof(T2), func));
    }

    public IEnumerator<Tuple<Type, Type, object>> GetEnumerator()
    {
        return objects.GetEnumerator();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return objects.GetEnumerator();
    }
}

Puis, quand on l'appelle enfin, on le fait avec réflexion:

var dependency = DependencyResolver.Current.GetService(search.Item1);
var methodInfo = search.Item2.GetMethod("Invoke");
return (IEnumerable<SearchResult>)methodInfo.Invoke(search.Item3, new Object[] { query, dependency });
2
Lucent Fox

Une autre possibilité consiste à utiliser la variable dynamique.

Par exemple:

Dictionary<string, List<dynamic>> d = new Dictionary<string, List<dynamic>>(); d.Add("Key", new List<dynamic>());

la variable dynamic résout le type à l'exécution.

1
pacoke

Que diriez-vous du dictionnaire en supposant que vous êtes sur C # 4

Dictionary<string, dynamic> Dict = new Dictionary<string, dynamic>();

Source: https://stackoverflow.com/a/5038029/3270733

0
lugao

Non, mais vous pouvez utiliser un objet au lieu d'un type générique.

Réponse longue: La version actuelle de C # ne vous permettra pas de saisir des entrées de type générique dans un dictionnaire. Vous avez le choix entre: a) créer une classe personnalisée identique à un dictionnaire, sauf lui permettre d’accepter des types génériques, ou b) obliger votre dictionnaire à prendre les valeurs de type objet. Je trouve que l'option b est l'approche la plus simple.

Si vous envoyez des listes de types spécifiques, lorsque vous allez traiter les listes, vous devrez les tester pour voir de quel type de liste il s’agit. Une meilleure approche consiste à créer des listes d'objets. De cette façon, vous pouvez entrer des nombres entiers, des chaînes ou tout autre type de données que vous voulez et vous n'avez pas nécessairement à tester pour voir quel type d'objet est contenu dans la liste. Cela produirait (vraisemblablement) l'effet recherché.

Voici un court programme de console qui fait le tour:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace dictionary
{
class Program
{
    static void Main(string[] args)
    {
        Dictionary<string, object> dic = new Dictionary<string, object>();
        var lstIntList = new List<object>();
        var lstStrings = new List<object>();
        var lstObjects = new List<object>();
        string s = "";

        lstIntList.Add(1);
        lstIntList.Add(2);
        lstIntList.Add(3);

        lstStrings.Add("a");
        lstStrings.Add("b");
        lstStrings.Add("c");

        dic.Add("Numbers", lstIntList);
        dic.Add("Letters", lstStrings);

        foreach (KeyValuePair<string, object> kvp in dic)
        {
            Console.WriteLine("{0}", kvp.Key);
            lstObjects = ((IEnumerable)kvp.Value).Cast<object>().ToList();

            foreach (var obj in lstObjects)
               {s = obj.ToString(); Console.WriteLine(s);}
            Console.WriteLine("");
        }


        Console.WriteLine("");
        Console.WriteLine("press any key to exit");
        Console.ReadKey();
    }//end main
}
}

 enter image description here

0
technoman23

Je suis arrivé à une implémentation sûre en utilisant ConditionalWeakTable .

public class FieldByType
{
    static class Storage<T>
        where T : class
    {
        static readonly ConditionalWeakTable<FieldByType, T> table = new ConditionalWeakTable<FieldByType, T>();

        public static T GetValue(FieldByType fieldByType)
        {
            table.TryGetValue(fieldByType, out var result);
            return result;
        }

        public static void SetValue(FieldByType fieldByType, T value)
        {
            table.Remove(fieldByType);
            table.Add(fieldByType, value);
        }
    }

    public T GetValue<T>()
        where T : class
    {
        return Storage<T>.GetValue(this);
    }

    public void SetValue<T>(T value)
        where T : class
    {
        Storage<T>.SetValue(this, value);
    }
}

Il peut être utilisé comme ceci:

/// <summary>
/// This class can be used when cloning multiple related objects to store cloned/original object relationship.
/// </summary>
public class CloningContext
{
    readonly FieldByType dictionaries = new FieldByType();

    public void RegisterClone<T>(T original, T clone)
    {
        var dictionary = dictionaries.GetValue<Dictionary<T, T>>();
        if (dictionary == null)
        {
            dictionary = new Dictionary<T, T>();
            dictionaries.SetValue(dictionary);
        }
        dictionary[original] = clone;
    }

    public bool TryGetClone<T>(T original, out T clone)
    {
        var dictionary = dictionaries.GetValue<Dictionary<T, T>>();
        return dictionary.TryGetValue(original, out clone);
    }
}
0
Orace