使用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,那么一切都很好。有什么想法吗?
答案 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;}
}
UniqueKey
或Name
将在实现类中明确地发挥作用,以防止不必要的(实际上是重复的)公共类成员。
答案 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;
}
注意:上面的代码未经过测试,可能需要调整