Collection qui autorise uniquement les éléments uniques dans .NET?

103

Existe-t-il une collection en C # qui ne vous permettra pas d'ajouter des éléments en double? Par exemple, avec la classe idiote de

public class Customer {
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Address { get; set; }

    public override int GetHashCode() {
        return (FirstName + LastName + Address).GetHashCode();
    }

    public override bool Equals(object obj) {
        Customer C = obj as Customer;
        return C != null && String.Equals(this.FirstName, C.FirstName) && String.Equals(this.LastName, C.LastName) && String.Equals(this.Address, C.Address);
    }
}

Le code suivant lèvera (évidemment) une exception:

Customer Adam = new Customer { Address = "A", FirstName = "Adam", LastName = "" };
Customer AdamDup = new Customer { Address = "A", FirstName = "Adam", LastName = "" };

Dictionary<Customer, bool> CustomerHash = new Dictionary<Customer, bool>();
CustomerHash.Add(Adam, true);
CustomerHash.Add(AdamDup, true);

Mais existe-t-il une classe qui garantira de la même manière l'unicité, mais sans KeyValuePairs? Je pensais HashSet<T>faire cela, mais après avoir lu la documentation, il semble que la classe n'est qu'une implémentation d'ensemble ( allez comprendre ).

Adam Rackis
la source
4
Je ne comprends pas votre problème avec HashSet<T>. MSDN indique «La classe HashSet <T> fournit des opérations d'ensemble de haute performance. Un ensemble est une collection qui ne contient aucun élément en double et dont les éléments ne sont pas dans un ordre particulier».
Daniel Hilgarth
5
Pouvez-vous expliquer plus pourquoi HashSet<T>est insuffisant?
JaredPar
@mootinator: La Dictionary<K,V>classe ne garantit aucun type d'ordre.
LukeH
3
Je suppose qu'il veut juste lancer une exception lorsque vous essayez d'ajouter une valeur existante ... Pour ce faire, vérifiez simplement la valeur booléenne retournée par la HashSet<T>.Addméthode et lancez quand false...
digEmAll
2
Il est également fortement recommandé de ne surcharger que ceux des types immuables . Un client mutable serait généralement mieux avec l'égalité de référence par défaut.
Henk Holterman

Réponses:

205

HashSet<T>est ce que vous recherchez. De MSDN (italiques ajoutés):

La HashSet<T>classe fournit des opérations d'ensemble hautes performances. Un ensemble est une collection qui ne contient aucun élément en double et dont les éléments ne sont pas dans un ordre particulier.

Notez que la HashSet<T>.Add(T item)méthode renvoie un bool- truesi l'élément a été ajouté à la collection; falsesi l'élément était déjà présent.

Donut
la source
9
L'élément T dans ce cas doit implémenter l'interface IEquatable. Si la classe n'hérite pas de cette interface, HashSet <T> ajoute des éléments en double.
Rudolf Dvoracek
Ou au lieu de l'implémentation de l'élément IEquatable, vous pouvez transmettre une implémentation (personnalisée) d' EqualityComparer<T>instance au HashSet<T>constructeur.
Sipke Schoorstra
17

Que diriez-vous simplement d'une méthode d'extension sur HashSet?

public static void AddOrThrow<T>(this HashSet<T> hash, T item)
{
    if (!hash.Add(item))
        throw new ValueExistingException();
}
Jonathon Reinhart
la source
13

À partir de la HashSet<T>page sur MSDN:

La classe HashSet (Of T) fournit des opérations d'ensemble hautes performances. Un ensemble est une collection qui ne contient aucun élément en double et dont les éléments ne sont pas dans un ordre particulier.

(c'est moi qui souligne)

Oded
la source
4

Si tout ce dont vous avez besoin est d'assurer l'unicité des éléments, alors HashSet est ce dont vous avez besoin.

Que voulez-vous dire quand vous dites "juste une implémentation d'ensemble"? Un ensemble est (par définition) une collection d'éléments uniques qui n'enregistre pas l'ordre des éléments.

Lloyd
la source
Vous avez tout à fait raison; la question était plutôt stupide. Fondamentalement, je cherchais quelque chose qui lèverait une exception lorsqu'un doublon était ajouté (comme Dictionary <TKey, TValue>), mais comme déjà mentionné, HashSet <T> renvoie false sur un doublon ajouté. +1, merci.
Adam Rackis
3

Juste pour ajouter mes 2 cents ...

si vous avez besoin d'un lancement d'exception ValueExistingException, HashSet<T>vous pouvez également créer votre collection facilement:

public class ThrowingHashSet<T> : ICollection<T>
{
    private HashSet<T> innerHash = new HashSet<T>();

    public void Add(T item)
    {
        if (!innerHash.Add(item))
            throw new ValueExistingException();
    }

    public void Clear()
    {
        innerHash.Clear();
    }

    public bool Contains(T item)
    {
        return innerHash.Contains(item);
    }

    public void CopyTo(T[] array, int arrayIndex)
    {
        innerHash.CopyTo(array, arrayIndex);
    }

    public int Count
    {
        get { return innerHash.Count; }
    }

    public bool IsReadOnly
    {
        get { return false; }
    }

    public bool Remove(T item)
    {
        return innerHash.Remove(item);
    }

    public IEnumerator<T> GetEnumerator()
    {
        return innerHash.GetEnumerator();
    }

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

cela peut être utile par exemple si vous en avez besoin dans de nombreux endroits ...

digEmAll
la source
Sûr. Je me demandais si quelque chose était intégré, mais merci +1
Adam Rackis
0

Vous pouvez consulter une sorte de liste unique comme suit

public class UniqueList<T>
{
    public List<T> List
    {
        get;
        private set;
    }
    List<T> _internalList;

    public static UniqueList<T> NewList
    {
        get
        {
            return new UniqueList<T>();
        }
    }

    private UniqueList()
    {            
        _internalList = new List<T>();
        List = new List<T>();
    }

    public void Add(T value)
    {
        List.Clear();
        _internalList.Add(value);
        List.AddRange(_internalList.Distinct());
        //return List;
    }

    public void Add(params T[] values)
    {
        List.Clear();
        _internalList.AddRange(values);
        List.AddRange(_internalList.Distinct());
       // return List;
    }

    public bool Has(T value)
    {
        return List.Contains(value);
    }
}

et vous pouvez l'utiliser comme suit

var uniquelist = UniqueList<string>.NewList;
uniquelist.Add("abc","def","ghi","jkl","mno");
uniquelist.Add("abc","jkl");
var _myList = uniquelist.List;

ne reviendra "abc","def","ghi","jkl","mno"toujours que lorsque des doublons y sont ajoutés

Vinod Srivastav
la source