Coleção que permite apenas itens exclusivos em .NET?

103

Existe uma coleção em C # que não permite adicionar itens duplicados a ela? Por exemplo, com a classe boba 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);
    }
}

O código a seguir (obviamente) lançará uma exceção:

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

Mas existe uma classe que irá garantir a exclusividade da mesma forma, mas sem KeyValuePairs? Eu pensei HashSet<T>que faria isso, mas depois de ler a documentação, parece que a classe é apenas uma implementação definida (veja a figura ).

Adam Rackis
fonte
4
Não entendo o seu problema com HashSet<T>. MSDN diz "A classe HashSet <T> fornece operações de conjunto de alto desempenho. Um conjunto é uma coleção que não contém elementos duplicados e cujos elementos não estão em uma ordem específica."
Daniel Hilgarth
5
Você pode explicar mais por que HashSet<T>é insuficiente?
JaredPar
@mootinator: A Dictionary<K,V>aula não garante nenhum tipo de pedido.
LukeH 01 de
3
Eu acho que ele só quer lançar uma exceção quando você tenta adicionar um valor existente ... Para fazer isso, basta verificar o valor bool retornado do HashSet<T>.Addmétodo e lançar quando false...
digEmAll
2
Também é altamente recomendável sobrecarregar apenas aqueles para tipos imutáveis . Um cliente mutável normalmente ficaria melhor com a igualdade de referência padrão.
Henk Holterman

Respostas:

205

HashSet<T>é o que você está procurando. Do MSDN (ênfase adicionada):

A HashSet<T>classe fornece operações de conjunto de alto desempenho. Um conjunto é uma coleção que não contém elementos duplicados e cujos elementos não estão em uma ordem específica.

Observe que o HashSet<T>.Add(T item)método retorna um bool- truese o item foi adicionado à coleção; falsese o item já estava presente.

Rosquinha
fonte
9
O item T, neste caso, deve implementar a interface IEquatable. Se a classe não herda essa interface, HashSet <T> adiciona elementos duplicados.
Rudolf Dvoracek
Ou, em vez da implementação do item IEquatable, você pode passar uma implementação (personalizada) da EqualityComparer<T>instância para o HashSet<T>construtor.
Sipke Schoorstra de
17

Que tal apenas um método de extensão no HashSet?

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

Na HashSet<T>página do MSDN:

A classe HashSet (Of T) fornece operações de conjunto de alto desempenho. Um conjunto é uma coleção que não contém elementos duplicados e cujos elementos não estão em uma ordem específica.

(ênfase minha)

Oded
fonte
4

Se tudo o que você precisa é garantir a exclusividade dos elementos, o HashSet é o que você precisa.

O que você quer dizer quando diz "apenas uma implementação definida"? Um conjunto é (por definição) uma coleção de elementos exclusivos que não salva a ordem dos elementos.

Lloyd
fonte
Você está completamente certo; a pergunta era meio estúpida. Basicamente, eu estava procurando por algo que gerasse uma exceção quando uma duplicata fosse adicionada (como Dictionary <TKey, TValue>), mas como já mencionado, HashSet <T> retorna falso em uma adição duplicada. 1, obrigado.
Adam Rackis
3

Só para adicionar meus 2 centavos ...

se você precisa de um ValueExistingException-throwing, HashSet<T>também pode criar sua coleção facilmente:

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

isso pode ser útil, por exemplo, se você precisar em muitos lugares ...

digEmAll
fonte
Certo. Eu queria saber se há algo integrado, mas obrigado +1
Adam Rackis
0

Você pode olhar para algo como uma Lista Única da seguinte maneira

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

e você pode usá-lo como segue

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

só retornará "abc","def","ghi","jkl","mno"sempre, mesmo quando duplicatas forem adicionadas a ele

Vinod Srivastav
fonte