Коллекция, которая позволяет только уникальные элементы в .NET?

106

Есть ли в C # коллекция, которая не позволяет добавлять в нее повторяющиеся элементы? Например, с глупым классом

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

Следующий код (очевидно) вызовет исключение:

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

Но существует ли класс, который аналогичным образом гарантирует уникальность, но без KeyValuePairs? Я думал, HashSet<T>что сделаю это, но, прочитав документацию, кажется, что класс - это просто установленная реализация (см. Рисунок ).

Адам Рэкис
источник
4
Я не понимаю вашей проблемы с HashSet<T>. MSDN сообщает: «Класс HashSet <T> обеспечивает высокопроизводительные операции над наборами. Набор - это коллекция, которая не содержит повторяющихся элементов и элементы которой не расположены в определенном порядке».
Дэниел Хилгарт 01
5
Не могли бы вы объяснить, почему этого HashSet<T>недостаточно?
JaredPar 01
@mootinator: Dictionary<K,V>класс не гарантирует никакого порядка.
LukeH 01
3
Я предполагаю, что он просто хочет вызвать исключение, когда вы пытаетесь добавить существующее значение ... Для этого просто проверьте значение типа bool, возвращаемое HashSet<T>.Addметодом, и выбросьте, когда false...
digEmAll 01
2
Также настоятельно рекомендуется перегружать их только для неизменяемых типов. Изменяемый Customer обычно лучше справляется со стандартным равенством по умолчанию.
Хенк Холтерман

Ответы:

208

HashSet<T>это то, что вы ищете. Из MSDN (выделено мной):

HashSet<T>Класс предоставляет набор операций с высокой производительностью. Набор - это коллекция, которая не содержит повторяющихся элементов и элементы которой не расположены в определенном порядке.

Обратите внимание, что HashSet<T>.Add(T item)метод возвращает bool- trueесли элемент был добавлен в коллекцию; falseесли элемент уже присутствовал.

Пончик
источник
10
Элемент T в этом случае должен реализовывать интерфейс IEquatable. Если класс не наследует этот интерфейс, HashSet <T> добавляет повторяющиеся элементы.
Рудольф Дворачек
Или вместо реализации элемента IEquatableвы можете передать конструктору (настраиваемую) реализацию EqualityComparer<T>экземпляра HashSet<T>.
Сипке Schoorstra
17

Как насчет простого метода расширения в HashSet?

public static void AddOrThrow<T>(this HashSet<T> hash, T item)
{
    if (!hash.Add(item))
        throw new ValueExistingException();
}
Джонатон Рейнхарт
источник
13

Со HashSet<T>страницы в MSDN:

Класс HashSet (Of T) обеспечивает высокопроизводительные операции над наборами. Набор - это коллекция, которая не содержит повторяющихся элементов и элементы которой не расположены в определенном порядке.

(курсив мой)

Одед
источник
4

Если все, что вам нужно, - это обеспечить уникальность элементов, то HashSet - это то, что вам нужно.

Что вы имеете в виду, когда говорите «просто набор реализаций»? Набор - это (по определению) набор уникальных элементов, который не сохраняет порядок элементов.

Ллойд
источник
Вы совершенно правы; вопрос был глупым. По сути, я искал что-то, что вызывало бы исключение при добавлении дубликата (например, Dictionary <TKey, TValue>), но, как уже упоминалось, HashSet <T> возвращает false при добавлении дубликата. +1, спасибо.
Адам Рэкис 01
3

Просто чтобы добавить свои 2 цента ...

если вам нужно исключение ValueExistingException, HashSet<T>вы также можете легко создать свою коллекцию:

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

это может быть полезно, например, если вам это нужно во многих местах ...

digEmAll
источник
Конечно. Мне было интересно, было ли что-нибудь встроено, но спасибо +1
Адам Рэкис 01
0

Вы можете изучить что-то вроде уникального списка следующим образом

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

и вы можете использовать его следующим образом

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

будет "abc","def","ghi","jkl","mno"всегда возвращаться, даже если к нему добавляются дубликаты

Винод Шривастав
источник