Kolekcja, która zezwala tylko na unikalne elementy w .NET?

103

Czy istnieje kolekcja w C #, która nie pozwoli Ci dodać do niej zduplikowanych elementów? Na przykład w przypadku głupiej klasy

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);
    }
}

Poniższy kod (oczywiście) zgłosi wyjątek:

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);

Ale czy istnieje klasa, która podobnie gwarantuje wyjątkowość, ale bez KeyValuePairs? Pomyślałem, że HashSet<T>to zrobię, ale po przeczytaniu dokumentacji wydaje się, że klasa to tylko zestaw implementacji ( rysunek ).

Adam Rackis
źródło
4
Nie rozumiem twojego problemu z HashSet<T>. MSDN mówi: „Klasa HashSet <T> zapewnia wysokowydajne operacje na zbiorach. Zestaw to kolekcja, która nie zawiera zduplikowanych elementów i której elementy nie są w określonej kolejności”.
Daniel Hilgarth
5
Czy możesz wyjaśnić więcej, dlaczego HashSet<T>jest to niewystarczające?
JaredPar
@mootinator: Dictionary<K,V>Klasa nie gwarantuje żadnego rodzaju zamówienia.
LukeH
3
Chyba po prostu chce rzucić wyjątek, gdy próbujesz dodać istniejącą wartość ... Aby to zrobić, po prostu sprawdź wartość bool zwróconą z HashSet<T>.Addmetody i wrzuć, gdy false...
digEmAll
2
Zdecydowanie zaleca się również przeciążanie tylko tych dla typów niezmiennych . Zmienny klient zwykle byłby lepszy z domyślną równością odniesienia.
Henk Holterman

Odpowiedzi:

205

HashSet<T>jest tym, czego szukasz. Z MSDN (podkreślenie dodane):

HashSet<T>Klasa zapewnia wysokiej wydajności zestaw operacji. Zestaw to zbiór, który nie zawiera zduplikowanych elementów i którego elementy nie są w określonej kolejności.

Zwróć uwagę, że HashSet<T>.Add(T item)metoda zwraca bool- truejeśli element został dodany do kolekcji; falsejeśli przedmiot był już obecny.

Pączek
źródło
9
Element T w tym przypadku powinien implementować interfejs IEquatable. Jeśli klasa nie dziedziczy tego interfejsu, HashSet <T> dodaje zduplikowane elementy.
Rudolf Dvoracek
Lub zamiast implementacji elementu IEquatablemożesz przekazać (niestandardową) implementację EqualityComparer<T>instancji do HashSet<T>konstruktora.
Sipke Schoorstra
17

A może po prostu metoda rozszerzenia w HashSet?

public static void AddOrThrow<T>(this HashSet<T> hash, T item)
{
    if (!hash.Add(item))
        throw new ValueExistingException();
}
Jonathon Reinhart
źródło
13

Ze HashSet<T>strony w MSDN:

Klasa HashSet (Of T) zapewnia operacje na zestawach o wysokiej wydajności. Zestaw to zbiór, który nie zawiera zduplikowanych elementów i którego elementy nie są w określonej kolejności.

(podkreślenie moje)

Oded
źródło
4

Jeśli wszystko, czego potrzebujesz, to zapewnienie niepowtarzalności elementów, to HashSet jest tym, czego potrzebujesz.

Co masz na myśli, mówiąc „tylko zestaw implementacji”? Zestaw jest (z definicji) zbiorem unikatowych elementów, które nie zapisują kolejności elementów.

Lloyd
źródło
Masz całkowitą rację; pytanie było trochę głupie. Zasadniczo szukałem czegoś, co spowodowałoby wyjątek po dodaniu duplikatu (na przykład Dictionary <TKey, TValue>), ale jak już wspomniano, HashSet <T> zwraca false przy zduplikowanym dodaniu. +1, dziękuję.
Adam Rackis
3

Dodam tylko moje 2 centy ...

jeśli potrzebujesz wyrzucania wyjątków ValueExistingException HashSet<T>, możesz również łatwo utworzyć swoją kolekcję:

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();
    }
}

może to być przydatne na przykład, jeśli potrzebujesz go w wielu miejscach ...

digEmAll
źródło
Pewnie. Zastanawiałem się, czy cokolwiek jest wbudowane, ale dziękuję +1
Adam Rackis
0

Możesz spojrzeć na coś w rodzaju Listy Unikalnej w następujący sposób

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);
    }
}

i możesz go używać w następujący sposób

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

powróci "abc","def","ghi","jkl","mno"zawsze, nawet jeśli zostaną do niego dodane duplikaty

Vinod Srivastav
źródło