类型参数的约束:接口与抽象类

时间:2011-09-20 17:35:26

标签: c# .net

使用Name属性创建项目的简单列表(UniqueList),该属性必须仅包含唯一项目(定义为具有不同的名称)。 UniqueList类型的约束可以是一个接口:

interface INamed
{
    string Name { get;}
}

或抽象类:

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

所以UniqueList可以是:

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

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

类函数AddUnique:

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

如果类类型约束基于接口,则在

中编译结果
Error:  Operator '==' cannot be applied to operands of type 'T' and 'T'

if (result == default(T))

如果我在抽象类上建立UniqueList,那么一切都很好。有什么想法吗?

9 个答案:

答案 0 :(得分:5)

那是因为接口可以应用于值类型的结构。要使其与界面一起使用,可以扩展约束,如下所示:

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

这将确保您无法将结构作为T传递,因此default(T)将评估为null,这正是您所期望的。


此外,我建议稍微概括一下UniqueList,允许使用不同类型的唯一键:

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

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

然后INamed接口可以很容易地声明为:

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

UniqueKeyName将在实现类中明确地发挥作用,以防止不必要的(实际上是重复的)公共类成员。

答案 1 :(得分:2)

我不明白为什么AddUnique必须使用Find并与default进行比较,您是否可以使用Count并与0进行比较?< / p>

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

UniqueList<T>似乎是使用HashSet创建的IEqualityComparer,用于比较T.Name

答案 2 :(得分:2)

我建议你实现IEquatable<>并将比较逻辑留给类。也不要将==用于引用类型(strings),因为它检查它是否是同一个对象,而不是它们是否相等。为了回应其他人,我建议您使用Dictionary<>检查唯一项目,或者只使用现有的KeyedCollection<string, INamed>来保存索引列表和字典。

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

答案 3 :(得分:1)

是。使用抽象类为您提供了==运算符的System.Object实现,而使用接口并不保证编译器该类型将支持==运算符,因为它可能是没有实现的结构。如果将class约束添加到类型参数中,则应该编译。

答案 4 :(得分:1)

该界面不提供Equals的实施,因此您无法比较这些项目。

顺便说一句,您似乎正在重新实现HashSet,请考虑使用官方提供的课程。

答案 5 :(得分:1)

以下是使用KeyedCollection<>基类的第二个答案。

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

}

答案 6 :(得分:1)

我将提出一个使用“鸭子打字”的解决方案。在这个实例中输入Duck意味着“如果它有一个名为 Name 的字符串属性,那么它是可用的。” Duck输入不依赖于接口或抽象基类。

我从KeyedCollection派生集合类,因为这个类已经提供了关键支持,但其他类,包括List<T>也是可能的。

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

}

第一次将类与特定类型参数一起使用时,我们使用动态代码生成来编译一个小方法。此方法以类型安全的方式读取name属性 - 无论容器类型如何!

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

答案 7 :(得分:1)

向T添加约束的建议是好的,因为现在的问题是 - 正如其他人所提到的 - 你的T不能保证实现==运算符。但是,它会实现Equals方法,所以你可以使用它......如果你不想将你的泛型与专有的基于参考或基于值的实现相提并论,这是很方便的。

在Blindy的评论之后,有一个警告。虽然Equals在所有对象类型上都是合法的,但如果您的源对象为null(即null.Equals(...)将抛出运行时错误),则它是不合法的,并且在T是类的情况下,它的默认值将为null,因此在尝试在空引用上调用Equals之前,您需要考虑该情况。

这是我的代码,它使用类和结构实现:

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

答案 8 :(得分:0)

您是否尝试过根据这一点定义界面?

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

然后在你的方法中做

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

注意:上面的代码未经过测试,可能需要调整