EDITAR: Après une inspection plus approfondie, cela ne semble pas être une bonne idée, avec moins de 60 éléments dans le hashset d'origine, la méthode ci-dessous semble être plus lente que la simple création d'un nouveau hashset.
CLAUSE DE NON-RESPONSABILITÉ : cela semble fonctionner mais à utiliser à vos propres risques, si vous allez sérialiser les hashsets clonés, vous voudrez probablement copier SerializationInfo m_siInfo.
J'ai également été confronté à ce problème et j'ai tenté de le résoudre. Vous trouverez ci-dessous une méthode d'extension qui utilise FieldInfo.GetValue et SetValue pour copier les champs requis. C'est plus rapide que d'utiliser HashSet(IEnumerable), combien dépend de la quantité d'éléments dans le hashset original. Pour 1 000 éléments, la différence est d'environ un facteur 7. Pour 100 000 éléments, elle est d'un facteur 3.
Il existe d'autres méthodes qui peuvent être encore plus rapides, mais cette méthode m'a permis d'éliminer le goulot d'étranglement pour l'instant. J'ai essayé d'utiliser les arbres d'expression et l'émission, mais j'ai rencontré un obstacle. Si je parviens à les faire fonctionner, je mettrai ce message à jour.
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.Serialization;
public static class HashSetExtensions
{
public static HashSet<T> Clone<T>(this HashSet<T> original)
{
var clone = (HashSet<T>)FormatterServices.GetUninitializedObject(typeof(HashSet<T>));
Copy(Fields<T>.comparer, original, clone);
if (original.Count == 0)
{
Fields<T>.freeList.SetValue(clone, -1);
}
else
{
Fields<T>.count.SetValue(clone, original.Count);
Clone(Fields<T>.buckets, original, clone);
Clone(Fields<T>.slots, original, clone);
Copy(Fields<T>.freeList, original, clone);
Copy(Fields<T>.lastIndex, original, clone);
Copy(Fields<T>.version, original, clone);
}
return clone;
}
static void Copy<T>(FieldInfo field, HashSet<T> source, HashSet<T> target)
{
field.SetValue(target, field.GetValue(source));
}
static void Clone<T>(FieldInfo field, HashSet<T> source, HashSet<T> target)
{
field.SetValue(target, ((Array)field.GetValue(source)).Clone());
}
static class Fields<T>
{
public static readonly FieldInfo freeList = GetFieldInfo("m_freeList");
public static readonly FieldInfo buckets = GetFieldInfo("m_buckets");
public static readonly FieldInfo slots = GetFieldInfo("m_slots");
public static readonly FieldInfo count = GetFieldInfo("m_count");
public static readonly FieldInfo lastIndex = GetFieldInfo("m_lastIndex");
public static readonly FieldInfo version = GetFieldInfo("m_version");
public static readonly FieldInfo comparer = GetFieldInfo("m_comparer");
static FieldInfo GetFieldInfo(string name)
{
return typeof(HashSet<T>).GetField(name, BindingFlags.Instance | BindingFlags.NonPublic);
}
}
}
0 votes
Hm, bonne question, juste par curiosité, quelles sont les inefficacités théoriques qui nous préoccupent ? je suis rouillé sur ma notation d'ordre pour les types de données abstraites, mais une vérification de l'existence dans l'ensemble de hachage cible ne serait-elle pas un simple test de collision O(1) ? je suis d'accord que d'un point de vue informationnel, cela pourrait être "mieux", mais pouvons-nous mettre une limite sur cela, et serait-elle significative ?
2 votes
Je soupçonne qu'ils n'ont pas de constructeur HashSet<T>(ISet<T>) parce que n'importe quelle classe pourrait implémenter ISet<T>, peut-être mal ; ce qui signifie que la présence d'ISet<T> n'est pas une garantie qu'il n'y a pas de duplicata.
2 votes
@Steve Ellinger, vous avez probablement raison. Cependant, ils auraient pu fournir un constructeur HashSet<T>(HashSet<T>)...
0 votes
En fait, ce que je suis curieux de savoir, c'est pourquoi ils n'ont pas implémenté ICloneable, est-ce parce que toute implémentation ne serait pas plus efficace que le constructeur que vous avez fini par appeler dans votre réponse référencée ; par conséquent, pourquoi s'embêter quand la fonctionnalité est déjà disponible. La même chose pourrait être dite pour votre constructeur de copie. Bien sûr, cela ne semble pas plausible étant donné votre commentaire sur "et vérifier si elle n'est pas déjà présente". Hmmm.
3 votes
Même le désérialiseur ne fait aucune supposition et utilise AddIfNotPresent(). Bonne idée, la culture a peut-être changé. C'est à éviter. Remettez en question la nécessité de cloner d'abord. Les opérations coûteuses doivent être, eh bien, coûteuses. Excellente conception de l'API.
0 votes
Connaîtriez-vous par hasard des inconvénients liés à l'utilisation de la sérialisation en général ? J'ai testé cette comparaison en utilisant le constructeur par rapport à la sérialisation et le constructeur était presque 2x plus rapide en moyenne sans vérification (3x avec vérification) sur un ensemble de 10000 éléments. Avec des ensembles plus grands, la différence est réduite avec le constructeur toujours plus rapide. Je peux poster le code si vous le souhaitez.
0 votes
@johnny g, je viens de faire un petit test : cloner en utilisant la réflexion contre l'appel au constructeur. Même avec la surcharge qu'elle implique, la réflexion est environ deux fois plus rapide. Donc je suppose qu'une vraie méthode Clone serait beaucoup plus rapide...
0 votes
@Hans Passant, bonnes remarques. Concernant la nécessité de cloner un hashset : comme je l'ai dit, la question est purement théorique, je n'ai pas besoin de le faire.
0 votes
@Jeff M, l'idée d'utiliser la sérialisation m'a traversé l'esprit, mais cela signifie que (1) les éléments doivent être sérialisables, et (2) chaque élément sera cloné... Mon idée était de faire une copie superficielle du hashset, pas une copie profonde.
0 votes
Ils n'ont pas implémenté ICloneable parce que c'est une interface minable. blogs.msdn.com/b/brada/archive/2003/04/09/49935.aspx est une des raisons.
0 votes
Pourquoi pensez-vous qu'ils n'ont pas un moyen efficace d'implémenter le clone via le constructeur ? Si j'écrivais le code, je vérifierais l'entrée, et si je savais que l'objet passé était un HashSet<T>, alors je pourrais écrire une méthode spécifique, plus rapide, pour faire la copie, puisque je peux déduire des informations à partir de cela, et aussi, accéder aux privés de l'entrée autre HashSet<T>, sinon il suffit de faire une copie "lente".
0 votes
@jasper : J'ai vérifié le code avec Reflector, il n'y a pas une telle optimisation.
0 votes
@Thomas Levesque : pas une surprise je suppose, mais c'est certainement une optimisation qui pourrait être faite. comme vous l'avez dit plus haut, c'est assez inefficace de recalculer quelque chose auquel vous avez déjà accès, et je ne vois aucune raison de ne pas le faire. encore une fois, si vous allez faire cela, il serait mieux de fournir un constructeur HashSet<T>(HashSet<T>) comme vous l'avez dit.