2009-11-20 58 views
2

假设我在C#中有一个简单的类Cat,类型的属性为string。现在我需要为我的猫收集类,所以我决定将Dictionary<string, Cat>包装在自定义集合类中。基本上,这个类包含一个私有字典变量,添加或通过其名称中删除集合成员的需要,以及索引猫:在C#中处理可变集合键

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

请参阅下面编辑我的帖子。 – 2009-11-20 19:13:10

回答

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

让我知道如果这不适合你。希望这可以帮助!

+0

嘿,谢谢。我知道List <>仅由int索引,所以使用Where这样的应该效率很低,对吧?我不期望有很大的收藏,所以这可能会奏效。 – CesarGon 2009-11-20 01:53:52

+0

“由int索引”应该“按位置索引”; p – CesarGon 2009-11-20 01:54:44

+1

在我看来,这不是一个可怕的解决方案。就像我在我的回答中提到的那样,集合的大小会影响对象访问的速度。此外,如果列表非常大并且您正在快速连续多次搜索元素,访问频率可能会对性能造成负面影响。我会试着想想另一种解决方案与您的原始示例相媲美! – Pwninstein 2009-11-20 01:58:26

1

你也可以把一个回调的猫,所以当它的名称属性更改您的收藏得到由具有Name setter方法通知

+0

我在我的问题中解释过,我尝试让Cat在名称更改时引发一个事件,并通过更新密钥来响应该事件的集合。你是这个意思吗? :-) – CesarGon 2009-11-20 02:01:55

4

...引发事件...

你的意思是这样的吗?

c1.Name = "Sofia"; 
NameChangedEventHandler handler = NameChangedEvent; 
if (handler != null) 
{ 
    handler(c1, new NameChangedEventArgs("Sophie", "Sophia")); 
} 

这是您具有二传手引发一个事件是什么意思?如果是这样,那么我会建议将其移至Cat类的Name属性设置器。没有理由要求设置器像这样提高事件。当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。这需要在前端做更多的努力,但在后端,你会有一个灵活的解决方案来适应各种情况。

+0

我所做的(以及我在我的问题中试图解释的)正是您的建议:从Cat的公共名称属性中的集合块提高事件。 :-) 耦合是可以的,因为事件毕竟不会将类连接起来。集合可以免费订阅事件。这很好(低耦合),也不好(因为没有办法强制一个集合保持其键最新)。这就是为什么我不太喜欢这个方法的原因。有人忘记订阅NameChanged事件并更新收集密钥相对容易。 – CesarGon 2009-11-20 02:07:20

+0

谢谢马特。非常感谢您的编辑。不过,Cats系列的全部重点都是为Cat提供强力型容器。不仅我不介意猫与猫紧密相连,它是如此的设计。我希望它们松散耦合的地方就是关键更新,即Cat的一个实例并不知道哪些集合持有对它的引用,所以基于事件的方法(本质上是松散耦合的)听起来不错我,原则上。不过,感谢关于INotifyPropertyChanged的想法,我不知道它。 :-) – CesarGon 2009-11-21 00:56:53