2011-09-20 7 views
5

Creación de una lista simple (UniqueList) de elementos con una propiedad Name que solo debe contener elementos únicos (definidos como que tienen diferentes nombres). La restricción del tipo UniqueList puede ser una interfaz:Restricciones sobre los parámetros de tipo: interfaz frente a clase abstracta

interface INamed 
{ 
    string Name { get;} 
} 

o una clase abstracta:

public abstract class NamedItem 
{ 
    public abstract string Name { get; } 
} 

Así que la UniqueList puede ser:

class UniqueList<T> : List<T> where T : INamed 

o

class UniqueList<T> : List<T> where T : NamedItem 

La función de clase, AddUni Que:

public T AddUnique(T item) 
{ 
    T result = Find(x => x.Name == item.Name); 
    if (result == default(T)) 
    { 
     Add(item); 
     return item; 
    } 
    return result; 
} 

Si la restricción de tipo de clase se basa en la interfaz, que compilan los resultados en

Error: Operator '==' cannot be applied to operands of type 'T' and 'T' 

en la línea de

if (result == default(T)) 

Todo está bien si me baso UniqueList en el resumen clase. ¿Alguna idea?

+1

¿Por qué no utilizar un '' HashSet para esto? Hace exactamente lo que quiere – BrokenGlass

+0

Si T es un tipo de valor, ¿Buscará devolver 'predeterminado (T)' cuando no se encuentre nada? Esto significaría que 'default (T)' no está permitido como un valor, que puede no ser su intención. –

+1

@BrokenGlass Pero no está creando un conjunto de cadenas, está creando un conjunto de cosas con nombre. Realmente quiere un 'HashSet ' con un 'IEqualityComparer ' – phoog

Respuesta

5

Esto se debe a que la interfaz se puede aplicar a una estructura que es un tipo de valor. Para hacerlo funcionar con la interfaz de extender la restricción de la siguiente manera:

class UniqueList<T> : List<T> where T : INamed, class 

que se asegurará de que no será capaz de pasar una estructura como Tdefault(T) y por lo tanto va a evaluar a null que es lo que esperas.


Además, me gustaría recomendar la generalización de su UniqueList un poco, lo que permite diferentes tipos de claves únicas:

interface IUnique<TKey> 
{ 
    TKey UniqueKey { get;} 
} 

class UniqueList<TItem,Tkey> : List<TItem> where TItem : IUnique<TKey>, class 

A continuación, la interfaz INamed puede declararse fácilmente como:

interface INamed : IUnique<string> 
{ 
    string Name { get;} 
} 

UniqueKey o Name se implmenened explícitamente en la clase de implementación para evitar innecesarios (duplicados de hecho) miembros de la clase pública.

1

Sí. El uso de la clase abstracta le da la implementación System.Object del operador ==, mientras que el uso de la interfaz no garantiza al compilador que el tipo admitirá el operador ==, ya que podría ser una estructura sin implementación. Si agrega la restricción class a su parámetro de tipo, debería compilarse.

1

La interfaz no proporciona una implementación para Equals, por lo que no puede comparar los elementos.

Por cierto, parece que está reimplementando HashSet, considere usar la clase oficial proporcionada en su lugar.

+0

No olvide que el método 'Equals' no es el mismo que el operador' == ' – phoog

+0

re: La interfaz no proporciona un implementación para Equals; No estoy seguro de su redacción aquí. Parece que no se garantiza que el operador exista, pero el método Equals debería funcionar, ¿no es así? Yo pensaría que reemplazar "resultado == predeterminado (T)" con "resultado. Clases (predeterminado (T))" lograría lo que Ol 'Badger está tratando de lograr. ¿O no es ese el caso? – Steven

+0

Pruébalo, no funcionará porque 'Equals' literalmente no es parte de la interfaz. Si lo restringe a "clase" o algo así, comenzará a funcionar. – Blindy

2

No entiendo por qué tiene que usar AddUniqueFind y comparar con default, no se puede utilizar Count y comparar con 0?

if Count(x => x.Name == item.Name) = 0 { 
    .... 

UniqueList<T> parece una HashSet creado con un IEqualityComparer que compara T.Name

+0

Buen punto. Eso simplifica las cosas y elimina la necesidad de una prueba de igualdad por separado. Además, Any (x => x.Name == item.Name) daría la respuesta como un booleano inicial, y podría * posiblemente * ser más eficiente (aunque, dado que esta propiedad es la base de la unicidad, nunca debería haber más de un elemento que satisfaga la condición, el método Count intentará contar todos los elementos que lo satisfagan, mientras que, desde mi comprensión, Any() se detendrá una vez que se encuentre el primer elemento satisfactorio) – Steven

2

Sugiero que implemente IEquatable<> y salir de la lógica de comparación de la clase. Tampoco use == para tipos de referencia (strings) ya que comprueba si es el mismo objeto, no si son iguales. Para hacer eco de otros, sugiero que use un Dictionary<> para buscar elementos únicos, o simplemente use el existente KeyedCollection<string, INamed> que mantiene una lista indexada y un diccionario.

public interface INamed : IEquatable<INamed> 
{ 
    string Name { get;} 
} 

public abstract class NamedItem : INamed 
{ 
    public abstract string Name { get; } 
    public bool Equals(INamed other) 
    { 
     if(other==null) return false;   
     return Name.Equals(other.Name); 
    } 
} 

public class UniqueList<T> : List<T> 
    where T : INamed 
{ 

    public T AddUnique(T item) 
    { 
     int index = FindIndex((x) => item.Equals(x)); 
     if (index < 0) 
     { 
      Add(item); 
      return item; 
     } 
     else 
     { 
      return this[index]; 
     } 
    } 
} 
0

¿Ha intentado definir su interfaz en algún punto de la línea de esto?

interface INamed : IEqualityComparer<T> 
{ 
    string Name { get;} 
} 

Y luego, en su método de hacer

public T AddUnique(T item) 
{ 
    T result = Find(x => x.Name == item.Name); 
    if (result.Equals(default(T))) 
    { 
     Add(item); 
     return item; 
    } 
    return result; 
} 

NOTA: el código de seguridad no se ha probado, puede ser necesario ajustar

1

Aquí está una segunda respuesta utilizando la clase KeyedCollection<> base.

class Program 
{ 

    static void Main(string[] args) 
    { 
     UniqueList<IntValue> list = new UniqueList<IntValue>(); 

     list.Add(new IntValue("Smile", 100)); 
     list.Add(new IntValue("Frown", 101)); 
     list.Add(new IntValue("Smile", 102)); // Error, key exists already 
     int x = list["Smile"].Value; 
     string frown = list[1].Name; 
    } 
} 

public interface INamed : IEquatable<INamed> 
{ 
    string Name { get;} 
} 

public abstract class NamedItem : INamed 
{ 
    public abstract string Name { get; } 
    public bool Equals(INamed other) 
    { 
     if(other==null) return false;   
     return Name.Equals(other.Name); 
    } 
} 

public class IntValue : NamedItem 
{ 
    string name; 
    int value; 

    public IntValue(string name, int value) 
    { 
     this.name = name; 
     this.value = value; 
    } 

    public override string Name { get { return name; } } 
    public int Value { get { return value; } } 
} 

public class UniqueList<T> : KeyedCollection<string, T> 
    where T : INamed 
{ 
    protected override string GetKeyForItem(T item) 
    { 
     return item.Name; 
    } 

} 
1

Propondré una solución usando "pato escribiendo". Duck typing en esta instancia significa "Si tiene una propiedad de cadena llamada Name, entonces es utilizable". Duck typing no depende de interfaces o clases base abstractas.

Derivo la clase de colección de KeyedCollection porque esta clase ya ofrece soporte clave, pero otras clases, incluyendo List<T> son posibles.

class NamedItemCollection<T> : KeyedCollection<string, T> { 

    private static readonly Func<T, string> keyProvider; 

    static NamedItemCollection() { 
     var x = Expression.Parameter(typeof(T), "x"); 
     var expr = Expression.Lambda<Func<T, string>>(
      Expression.Property(x, "Name"), 
      x); 
     keyProvider = expr.Compile(); 
    } 

    protected override string GetKeyForItem(T item) { 
     return keyProvider(item); 
    } 

} 

La primera vez que la clase se utiliza con un parámetro de tipo específico, se utiliza la generación de código dinámica para compilar un método pequeño. Este método lee la propiedad del nombre de una manera segura, ¡sin importar el tipo de contenedor!

public abstract class NamedItem { public abstract string Name { get; } } 
struct Thing { public string Name { get; set; } } 

var namedItems1 = new NamedItemCollection<NamedItem>(); 
var namedItems2 = new NamedItemCollection<Thing>(); 
var namedItems3 = new NamedItemCollection<Type>(); 
1

Las sugerencias para añadir una restricción a su T son buenos, porque el problema tal y como está es - como otros han mencionado - que su T no está garantizada para poner en práctica el operador ==. Sin embargo, implementará el método Equals, por lo que podría usarlo en su lugar ... esto es útil si no quiere necesariamente contrain sus genéricos a una referencia exclusivamente basada en valores o implementaciones.

Después de los comentarios de Blindy, hay una advertencia. Si bien Equals es legítimo en todos los tipos de objetos, no es legítimo si el objeto de origen es nulo (es decir, null.Equals (...) arrojará un error de tiempo de ejecución), y en el caso de que T sea una clase, su valor predeterminado será nulo, por lo que tendrá que dar cuenta de esa situación antes de tratar de invocar Equals en una referencia nula.

Aquí está mi código, que utiliza tanto la clase una implementación estructura:

public interface INamed 
{ 
    string Name { get; set; } 
} 

public class Foo<T> 
    : List<T> 
    where T : INamed 
{ 
    public bool IsUnique(T item) 
    { 
     T result = Find(x => x.Name == item.Name); 
     if (result == null || result.Equals(default(T))) 
      return true; 
     return false; 
    } 
} 

public class BarClass : INamed 
{ 
    public string Name { get; set; } 
} 
public struct BarStruct : INamed 
{ 
    public string Name { get; set; } 
} 
[STAThread] 
static void Main() 
{ 
    BarClass bc = new BarClass { Name = "test" }; 
    Foo<BarClass> fc = new Foo<BarClass>(); 
    fc.IsUnique(bc); 

    BarStruct bs = new BarStruct { Name = "test" }; 
    Foo<BarStruct> fs = new Foo<BarStruct>(); 
    fs.IsUnique(bs); 
} 
Cuestiones relacionadas