假设我在C#中有一个简单的类Cat
,其Name
属性类型为string
。现在我需要一个我的猫的集合类,所以我决定将Dictionary<string, Cat>
包装在自定义集合类中。基本上,这个类包含一个私有字典变量,并根据需要添加或删除集合成员,以及按名称索引猫:
class Cats
{
private Dictionary<string, Cat> m_dic = new Dictionary<string,Cat>();
public void Add(Cat c)
{
m_dic.Add(c.Name, c);
}
public void Remove(string name)
{
m_dic.Remove(name);
}
public Cat this[string name]
{
get
{
return m_dic[name];
}
}
}
现在我可以创建一个集合和猫,就像这样:
Cats cs = new Cats();
cs.Add(new Cat("Valentina"));
cs.Add(new Cat("Sophie"));
cs.Add(new Cat("Tomboy"));
我可以从名字中找回一只猫:
Cat c1 = cs["Sophie"];
这一切都非常好。问题是,当我改变猫的名字时,就像这样:
c1.Name = "Sofia";
...显然,c1
引用的对象的集合键未更新。因此,如果我尝试使用新名称检索相同的项目,我会得到一个例外:
Cat c2 = cs["Sofia"]; //KeyNotFoundException is thrown here.
运行时这是正确且明显的行为。我的问题是:每当元素的name属性发生变化时,你能建议一种优雅可靠的方法来改变集合键吗?
我的目标是能够随时从其名称中检索项目,如您所想。我已经通过让Name
属性的setter引发一个事件来解决这个问题,这样任何持有该对象的集合都可以更新相应的键。不过,这种方法非常麻烦且效率不高。
你能想到更好的事吗?谢谢。
答案 0 :(得分:4)
您的收藏有多大,以及通过索引检索项目有多重要?
如果它相对较小(数百,而不是数千),最好使用List<Cat>
,并使用新的LINQ扩展方法访问它们,例如:
public Cat this[string name]{
get{
//Will return the first Cat in the list (or null if none is found)
return m_List.Where(c => c.Name == name).FirstOrDefault();
}
}
添加(和删除)也很简单:
public void Add(Cat c){
m_List.Add(c);
}
如果这不适合你,请告诉我。希望这有帮助!
答案 1 :(得分:4)
...拥有
Name
的设定者 财产引发了一场事件......
你的意思是这样吗?
c1.Name = "Sofia";
NameChangedEventHandler handler = NameChangedEvent;
if (handler != null)
{
handler(c1, new NameChangedEventArgs("Sophie", "Sophia"));
}
这是 setter 举办活动的意思吗?如果是这样,那么我建议将其移动到Name
类的Cat
属性设置器。没有理由要求 setters 像这样引发事件。当Cat
的名称通过公共财产发生变化时,应该隐式执行此操作。
对我来说,这个是一个优雅的解决方案;它只是不符合Dictionary
集合的工作方式。我不知道这本身就是一个问题,但它确实将Cats
集合紧密地耦合到Cat
类。
请记住,您可能希望实现许多与通用Dictionary
类相同的接口。否则,Cats
集合在某些方面将类似Dictionary
,但不完全。
编辑:这是对您的评论的回应。我希望我能更清楚地传达我的想法。我的意图是改进你的设计。
我同意,一般,事件确实提供了更宽松的耦合级别。但是,在这种情况下,Cats
集合仍然与Cat
类紧密耦合,因为集合正在注册特定类型的类所暴露的特定类型的事件。
那么如何改进呢?
一种直接的改进方法是让Cat
类实现在接口中定义的事件。 .NET为此明确目的提供了这样的接口 - System.ComponentModel
命名空间中的INotifyPropertyChanged
接口。通过在Cat
类中实现此接口,这将允许Cats
集合包装器定义如下:
class Cats
{
private Dictionary<string, INotifyPropertyChanged> m_dic =
new Dictionary<string, INotifyPropertyChanged>();
public void Add(INotifyPropertyChanged obj)
{
m_dic.Add(obj.Name, obj);
}
public void Remove(string name)
{
m_dic.Remove(name);
}
public INotifyPropertyChanged this[string name]
{
get { return m_dic[name]; }
}
}
看到改进?该系列现在更加灵活。它可以包含任何实现INotifyPropertyChanged
接口的类型。换句话说,它不再与Cat
类相关联。
但是,它仍然要求字典中存储的任何值都实现Name
属性(请参阅Add()
方法),因此仍有一些工作要做。
最终,您希望集合保存提供string
属性的对象以用作键值。解决方案是将其定义为接口。
public interface INotificationKey : INotifyPropertyChanged
{
string Key { get; set; }
}
请注意,INotificationKey
接口继承自INotifyPropertyChanged
接口,允许集合包装器定义如下:
class NotificationDictionary
{
private Dictionary<string, INotificationKey> m_dic =
new Dictionary<string, INotificationKey>();
public void Add(INotificationKey obj)
{
m_dic.Add(obj.Key, obj);
}
public void Remove(string key)
{
m_dic.Remove(key);
}
public INotificationKey this[string key]
{
get { return m_dic[key]; }
}
}
这是一个非常灵活的解决方案。但它仍然不足,因为它不像Dictionary
那样完全行事。例如,如定义的那样,NotificationDictionary
类不能在foreach
迭代中使用,因为它没有实现IEnumerable<>
接口。
要成为真正优雅的解决方案,该集合的行为就像Dictionary
一样。这需要在前端稍作努力,但在后端,您将拥有足够灵活的解决方案以适应各种情况。
答案 2 :(得分:1)
你也可以在Cat上进行回调,这样当它的Name属性发生变化时,你的收藏就会得到通知