在C#中处理可变集合键

时间:2009-11-20 01:41:12

标签: c# .net collections class-design

假设我在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引发一个事件来解决这个问题,这样任何持有该对象的集合都可以更新相应的键。不过,这种方法非常麻烦且效率不高。

你能想到更好的事吗?谢谢。

3 个答案:

答案 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属性发生变化时,你的收藏就会得到通知