2
votes

Comment rechercher rapidement des objets de certaines classes ou classes enfants en C #

Je veux créer une collection d'objets de différentes classes, et être capable de rechercher rapidement toutes les instances qui peuvent être assignées à une classe spécifique, sans avoir à parcourir la liste entière. Je peux utiliser un Dictionary > , mais cela ne me trouvera pas toutes les classes / interfaces enfants.

ObjHolder o = new ObjHolder();
o.AddObject(new Child());
o.AddObject(new Other());
o.GetObjectsOfType<Parent>(); // Returns an empty list

Maintenant, cela va fonctionne très bien pour ce qui suit:

ObjHolder o = new ObjHolder();
o.AddObject(new Parent());
o.AddObject(new Other());
o.GetObjectsOfType<Parent>(); // Returns only the Parent object    

Mais cela ne fonctionnera pas dans le cas suivant:

public class Parent {
}

public class Child : Parent {
}

public class Other {
}


public class ObjHolder {
    Dictionary<System.Type, List<object>> objs = new Dictionary<System.Type, List<object>>();

    public void AddObject(object obj) {
        if (!objs.ContainsKey(obj.GetType()) {
            objs[obj.GetType()] = new List<object>();
        }

        objs[obj.GetType()].Add(obj);
    }

    public List<object> GetObjectsOfType<T>() {
        return objs.ContainsKey(typeof(T)) ? objs[typeof(T)] : new List<object>();
    }
}

Je veux être capable de saisir tous les objets qui peuvent être assignés à Parent, et qui inclut l'objet Child, mais le code ne le retournera pas.

Des idées pour y parvenir de manière efficace? p>


2 commentaires

Qu'en est-il des dictionnaires supplémentaires Dictionary > contenant les classes enfants?


Cela peut signifier trop de dictionnaires, car une classe peut implémenter de nombreuses interfaces, chacune avec sa propre interface de base, etc.


4 Réponses :


1
votes

Si je comprends bien, si B hérite de A et que vous recherchez A, vous voulez également renvoyer tous les objets B.

Je le ferais de cette façon: Modifiez votre AddObject existant méthode pour ajouter l'objet à plusieurs collections. Vous pouvez utiliser la propriété Type.BaseType pour obtenir le type de base. Faites-le en boucle, jusqu'à ce que vous atteigniez la classe object .

public void AddObject(object obj) {
    Type type;
    do {
        type = obj.GetType();
        if (!objs.ContainsKey(type) {
            objs[type] = new List<object>();
        }
        objs[type] = obj;
        if(type == typeof(object)) break;
        type = type.BaseType;
    } while(true);
}


2 commentaires

Hmm, cela pourrait être une bonne direction, mais qu'en est-il des interfaces? J'aurais également besoin d'itérer toutes les interfaces et toutes leurs interfaces de base, ce qui pourrait finir un peu trop gros ... C'est une option, mais peut-être qu'il existe de meilleurs moyens plus efficaces?


Pour les interfaces, vous pouvez appeler .GetInterfaces () sur la variable de type. Un appel sur le type le plus spécifique devrait suffire, il contiendra toutes les interfaces, y compris les interfaces des types de base.



0
votes

Vous devez vérifier que si la liste mise en cache contient un objet Assignable de type T . Essayez le code ci-dessous qui vérifie cette relation

public void AddObject(object obj)
{
    if (!objs.ContainsKey(obj.GetType()))
    {
        objs[obj.GetType()] = new List<object>() { obj };
    }
    else
    {
        objs[obj.GetType()].Add(obj);
    }
}

Je sais que votre code ci-dessus est juste pour illustrer le cas, mais il y a un bogue dans la fonction AddObject , vous enregistrer un objet plutôt qu'une liste, la fonction devrait être quelque chose comme ceci:

public List<object> GetObjectsOfType<T>()
{
   foreach (var pair in objs)
   {
      if (pair.Key == typeof(T) || typeof(T).IsAssignableFrom(pair.Key))
      {
         return pair.Value;
      }
   }

   return new List<object>();
}


1 commentaires

Idée intéressante, mais pas parfaite car elle nécessite encore d'itérer tous les types jamais ajoutés, pour chaque extraction.



2
votes

Vous pouvez utiliser le Méthode Type.IsAssignableFrom pour déterminer quelles classes peuvent être affectées au générique.

Modifiez la méthode GetObjectsOfType comme suit:

objs[obj.GetType()].Add(obj);


2 commentaires

Idée intéressante, mais pas parfaite car elle nécessite encore d'itérer tous les types jamais ajoutés, pour chaque extraction.


Si vous ne souhaitez pas itérer tous les types, vous devez ajouter tous les objets au même type de base.



2
votes

Donc, ce que vous voulez, c'est quelque chose de similaire au dictionnaire. Essayons de le mettre en œuvre étape par étape. Mon code est fortement commenté, donc je vais laisser le code parler. En gros, tout ce que vous avez à faire est de laisser des miettes de pain en cours de route lorsque vous ajoutez votre objet dans votre porte-objet.

var a = new A();
var b = new B();
var c = new C();
var objectholder = new Objectholder();
objectholder.AddObject(a);
objectholder.AddObject(b);
objectholder.AddObject(c);
// Contains A and B and C
var allA = objectholder.GetObjectsOf<A>().ToArray();

Enfin, jetons un œil à ObjectHolder comme je l'imaginais. Le plus dur dans votre cas, je pense que vous n'avez pas gardé le principe de responsabilité unique et que vous avez essayé de tout faire en une seule fois.

 public class Objectholder
 {
    /// <summary>
    /// Holds Type vs object information.
    /// Each object is seperated into its own Type.
    /// </summary>
    private readonly ConcurrentDictionary<Type, List<object>> _dict = new ConcurrentDictionary<Type, List<object>>();

    /// <summary>
    /// I already explained about this class before so here I will pass.
    /// </summary>
    private readonly InheritanceInfo inheritanceInfo = new InheritanceInfo();

    /// <summary>
    /// Adds an object to ObjectHolder.
    /// </summary>
    /// <param name="obj">Object to add</param>
    public void AddObject(object obj) {
        _dict.AddOrUpdate(obj.GetType(), t => AddValueFactory(obj), (t, li) => UpdateValueFactory(obj, li));
    }

    /// <summary>
    /// Gets Objects which are assignable to type of T.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    public IEnumerable<T> GetObjectsOf<T>() {
        var tree = inheritanceInfo.GetAssignables(typeof(T)).Concat(new[] { typeof(T) });
        return tree.SelectMany(t => _dict[t]).Cast<T>();
    }

    /// <summary>
    /// Adds a value to dictionary.
    /// </summary>
    /// <param name="obj">Object to add.</param>
    /// <returns></returns>
    private List<object> AddValueFactory(object obj)
    {
        inheritanceInfo.Update(obj.GetType());
        var l = new List<object>();
        l.Add(obj);
        return l;
    }

    /// <summary>
    /// Updates a value in dictionary.
    /// </summary>
    /// <param name="obj">Object to add.</param>
    /// <param name="li">List of objects</param>
    /// <returns></returns>
    private List<object> UpdateValueFactory(object obj, List<object> li)
    {
        inheritanceInfo.Update(obj.GetType());
        li.Add(obj);
        return li;
    }
}

// Mock classes
public class A { }
public class B : A { }
public class C : B { }

L'utilisation de ObjectHolder code> correspond à ce que vous vouliez faire.

public class InheritanceInfo
{
    private class InheritanceChain
    {
        /// <summary>
        /// LinkedList which holds inheritance chain from least derived to most derived for a given Type.
        /// </summary>
        private readonly LinkedList<Type> _linkedList = new LinkedList<Type>();

        /// <summary>
        /// Creates an Inheritance chain for a given type which holds information about base types.
        /// </summary>
        /// <param name="t">Type for which inheritance chain will be created.</param>
        public InheritanceChain(Type t)
        {
            Type type = t;
            do
            {
                if (type == typeof(object))
                {
                    break;
                }
                _linkedList.AddFirst(type);
                type = type.BaseType;
            } while (true);
        }

        /// <summary>
        /// First element of LinkedList. This will be used for iteration.
        /// </summary>
        public LinkedListNode<Type> First => _linkedList.First;
    }

    /// <summary>
    /// Dictionary which holds Type vs DerivedTypes information. Basically does the all handling.
    /// </summary>
    private readonly ConcurrentDictionary<Type, SingleStepDerivedTypes> _inheritanceDictionary;

    /// <summary>
    /// InheritanceInfo class holds information about each Type's inheritance tree.
    /// Each Type holds information about one step down the inheritance tree.
    /// Example: public class C:B{}
    ///          public class B:A{}
    ///          public class A  {}
    /// Inheritance infor for class A holds info about only B because C is derived from B and
    /// it is not a direct descendant of A.
    /// </summary>
    public InheritanceInfo() {
        _inheritanceDictionary = new ConcurrentDictionary<Type, SingleStepDerivedTypes>();
    }

    /// <summary>
    /// Updates the given Type inheritance tree info.
    /// </summary>
    /// <param name="type"></param>
    public void Update(Type type) {
        var element = new InheritanceChain(type).First;
        while (element.Next != null) {
            _inheritanceDictionary.AddOrUpdate(element.Value, (_)=>AddValueFactory(element.Next.Value), (_,sdt)=>UpdateValueFactory(element.Next.Value,sdt));
            element = element.Next;
        }
    }

    /// <summary>
    /// Gets all the assignable types for the given type t.
    /// </summary>
    /// <param name="t">Type for which assignable types will be searched.</param>
    /// <returns>All the assignable types for Type t.</returns>
    public IEnumerable<Type> GetAssignables(Type t)
    {
        if(_inheritanceDictionary.TryGetValue(t ,out var derivedTypes) == false) {
            return Array.Empty<Type>();
        }
        var recursive = derivedTypes.GetTypes().SelectMany(tp=>GetAssignables(tp));
        return recursive.Concat(derivedTypes.GetTypes());
    }

    /// <summary>
    /// Add value to the dictionary
    /// </summary>
    /// <param name="t">Type to add to ConcurrentDictionary</param>
    /// <returns>SingleStepDerivedTypes which holds information about derived type t</returns>
    private static SingleStepDerivedTypes AddValueFactory(Type t) {
        var s = new SingleStepDerivedTypes();
        s.Add(t);
        return s;
    }

    /// <summary>
    /// Updates the already created SingleStepDerivedTypes object.
    /// </summary>
    /// <param name="t">Type to add</param>
    /// <param name="sdt">SingleStepDerivedTypes</param>
    /// <returns>Updated SingleStepDerivedTypes.</returns>
    private static SingleStepDerivedTypes UpdateValueFactory(Type t, SingleStepDerivedTypes sdt) {
        sdt.Add(t);
        return sdt;
    }
}



public class SingleStepDerivedTypes
{
    /// <summary>
    /// HashSet which holds information about derived Types.
    /// </summary>
    private readonly HashSet<Type> _singleStepDerivedTypes;

    /// <summary>
    /// Constructor ;)
    /// </summary>
    public SingleStepDerivedTypes() {
        _singleStepDerivedTypes = new HashSet<Type>();
    }

    /// <summary>
    /// Adds a Type to the Derived Type information.
    /// </summary>
    /// <param name="type">Type to add.</param>
    public void Add(Type type) {
        _singleStepDerivedTypes.Add(type);
    }

    /// <summary>
    /// Gets the contained information about types.
    /// </summary>
    /// <returns>IEnumerable of Types contained in this object.</returns>
    public IEnumerable<Type> GetTypes() {
        return _singleStepDerivedTypes;
    }
}


6 commentaires

Donc si je comprends bien, lorsqu'un objet est ajouté, vous revenez à tous ses types de base et pour chaque sauvegarde les types dérivés qui mènent à cet objet. Ensuite, lorsqu'un type est demandé, vous utilisez votre cache pour rechercher toutes les classes dérivées du type demandé et auxquelles un objet est ajouté. C'est assez bon, mais ne gère pas les interfaces, ce qui est une exigence. Le problème est que si nous commençons à itérer toutes les interfaces et leurs interfaces de base, cela peut devenir un peu fou ...


Oh ouais à peu près comme tu l'as dit. Supposons cette hiérarchie C: B: A Si j'ajoute un C , je l'ajoute au groupe C mais laisse un cache auquel C appartient à B et qui appartient à A. Vous pouvez également ignorer cette étape (ce que je veux dire, c'est que l'arbre d'héritage de chaque type ne change pas) et ne le faire qu'une seule fois. Pour les interfaces, en fait rien ne change. Vous pouvez appliquer la même logique que j'ai publiée ici. Il vous suffit de changer quelques lignes alors c'est tout. Laissez-moi vous donner un indice. Vous commencerez par typeof (T) .GetInterfaces () puis suivrez la même boucle


@ tbkn23, Le problème est que si nous commençons à itérer toutes les interfaces et leurs interfaces de base, cela peut devenir un peu fou ... ← Ce n'est pas du tout un problème. Comme je l'ai mentionné dans mes commentaires, tout ce que vous avez à faire est de laisser un fil d'Ariane sur quel type implémente quelle interface


@ tbkn23, Avez-vous été en mesure de comprendre la partie interfaces? :)


Oui, la mise en œuvre n'est pas le problème. Ce qui m'inquiète, c'est que vous ajoutez un objet de type Dictionary . Cela signifierait des dizaines d'interfaces. Mais je suppose que ce ne serait pas trop mal puisque nous n'avons besoin de le faire qu'une seule fois lors de l'ajout d'un objet d'un certain type pour la première fois, comme vous l'avez dit, les liens ne changent pas. Pouvez-vous mettre à jour votre réponse pour refléter ces deux changements afin que je puisse l'accepter? (ne laissez le fil d'Ariane que lors de l'ajout d'un type pour la première fois et incluez les interfaces)


Ce qui m'inquiète, c'est que vous ajoutez un objet de type Dictionary Il n'y a rien à craindre. Vous descendrez une fois dans l'arbre d'héritage.