¿Colección que permite solo elementos únicos en .NET?

103

¿Hay una colección en C # que no le permita agregarle elementos duplicados? Por ejemplo, con la tonta clase 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);
    }
}

El siguiente código (obviamente) lanzará una excepción:

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

Pero, ¿hay una clase que garantice de manera similar la singularidad, pero sin KeyValuePairs? Pensé HashSet<T>que haría eso, pero después de leer los documentos, parece que la clase es solo una implementación establecida ( figura ).

Adam Rackis
fuente
4
No entiendo tu problema con HashSet<T>. MSDN dice "La clase HashSet <T> proporciona operaciones de conjuntos de alto rendimiento. Un conjunto es una colección que no contiene elementos duplicados y cuyos elementos no están en ningún orden en particular".
Daniel Hilgarth
5
¿Puede explicar más por qué HashSet<T>es insuficiente?
JaredPar
@mootinator: La Dictionary<K,V>clase no garantiza ningún tipo de pedido.
LukeH
3
Supongo que solo quiere lanzar una excepción cuando intentas agregar un valor existente ... Para hacer esto, simplemente verifica el valor bool devuelto por el HashSet<T>.Addmétodo y lanza cuando false...
digEmAll
2
También se recomienda encarecidamente sobrecargar solo los de tipos inmutables . Un Cliente mutable normalmente estaría mejor con la igualdad de referencia predeterminada.
Henk Holterman

Respuestas:

205

HashSet<T>es lo que estás buscando. De MSDN (énfasis agregado):

La HashSet<T>clase proporciona operaciones de conjuntos de alto rendimiento. Un conjunto es una colección que no contiene elementos duplicados y cuyos elementos no están en un orden particular.

Tenga en cuenta que el HashSet<T>.Add(T item)método devuelve un bool- truesi el elemento se agregó a la colección; falsesi el artículo ya estaba presente.

Rosquilla
fuente
9
El elemento T en este caso debería implementar la interfaz IEquatable. Si la clase no hereda esta interfaz, HashSet <T> agrega elementos duplicados.
Rudolf Dvoracek
O en lugar de implementar el elemento IEquatable, puede pasar una implementación (personalizada) de la EqualityComparer<T>instancia al HashSet<T>constructor.
Sipke Schoorstra
17

¿Qué tal un método de extensión en HashSet?

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

Desde la HashSet<T>página en MSDN:

La clase HashSet (Of T) proporciona operaciones de conjuntos de alto rendimiento. Un conjunto es una colección que no contiene elementos duplicados y cuyos elementos no están en un orden particular.

(énfasis mío)

Oded
fuente
4

Si todo lo que necesita es garantizar la unicidad de los elementos, entonces HashSet es lo que necesita.

¿A qué se refiere cuando dice "solo una implementación establecida"? Un conjunto es (por definición) una colección de elementos únicos que no guarda el orden de los elementos.

Lloyd
fuente
Tienes toda la razón; la pregunta fue algo estúpida. Básicamente, estaba buscando algo que arrojara una excepción cuando se agregaba un duplicado (como Dictionary <TKey, TValue>), pero como ya se mencionó, HashSet <T> devuelve falso en un complemento duplicado. +1, gracias.
Adam Rackis
3

Solo para agregar mis 2 centavos ...

si necesita un lanzamiento de ValueExistingException, HashSet<T>también puede crear su colección fácilmente:

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

esto puede ser útil, por ejemplo, si lo necesita en muchos lugares ...

digEmAll
fuente
Por supuesto. Me preguntaba si había algo integrado, pero gracias +1
Adam Rackis
0

Puede buscar una especie de lista única de la siguiente manera

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

y puedes usarlo como sigue

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

solo volverá "abc","def","ghi","jkl","mno"siempre incluso cuando se le agreguen duplicados

Vinod Srivastav
fuente