如何在C# 4.0中检测通用列表的添加?

8
我有一个名为LocationList的List子类。这是我们添加其他属性(如IsExpanded等)以在UI中使用的方便方法。但是现在,我们希望每个位置都知道其父级。因此,当向LocationList添加内容时,我们需要被通知,但无法覆盖Add和Remove。虽然我们可以使用ObservableCollection,但那更多是View/ViewModel构造。这是纯模型数据。
当然,我们可以在将其添加到集合中时手动设置父级,但我讨厌像这样的非自动化逻辑,因为没有任何东西来强制执行其正确设置。让集合(好吧,List)自动说“嘿...你正在被添加,所以我会将你的父级设置为拥有我的类。”是我想要的。
我的想法是不再对List进行子类化,而是创建一个直接使用List的类,然后我可以简单地公开自己的Add/Remove方法并在其中处理对“Parent”的调整。然而,这样做会破坏能够枚举内部集合的能力,因为它在内部。
话虽如此,是否有任何方法可以监听List的更改,或者至少从包装对象委托其IEnumerable接口到其包装器?
6个回答

9
将你的LocationList从继承List<T>更改为继承Collection<Location>。我不知道为什么List<T>没有被封装,但它是不可扩展的。
来源:《框架设计指南第二版》第251页:

List<T>在性能和功能方面进行了优化,但牺牲了API的清晰度和灵活性。


这似乎是最容易实现的...只需将List<T>更改为Collection<T>,这不会增加ObservableCollection<T>的所有开销,尽管与List<T>相比,您会失去一些性能。 不过,那已经足够好了,所以你得到了我的投票。谢谢! - Mark A. Donohoe

4
List<T>有一些受保护的成员,例如InsertItem<T>RemoveItem<T>等,您可以在派生类中重写这些成员以实现您想要的功能。
**更新**
实际上,上述内容是不正确的,这些受保护的方法属于Collection<T>。通常情况下,在派生自定义List类时,建议从Collection<T>而不是List<T>派生。
请参见此答案

1
如果你要重写InsertItem/RemoveItem,为什么不直接使用已经完成工作的版本(ObservableCollection)呢? - Ana Betts
ObservableCollection 更适合 ViewModel 而非 Model 进行优化。唯一需要知道变化的人是集合本身。您不需要完整的更改通知开销。 - Mark A. Donohoe
1
@MarqueIV:“我现在甚至不能接受你的答案,还要再等11分钟。”好在最初的答案是错误的——太过草率。请查看更新并接受Michael Stum的答案! - Joe
@Joe,我可能说得太早了。 我在List<T>的智能感知中没有看到这两个项目,文档中也没有看到。 你确定那是正确的类吗?(http://msdn.microsoft.com/en-us/library/6sh2ey19.aspx) - Mark A. Donohoe
@Joe,我看到这些在Collection<T>上,而不是List<T>。那是你的意思吗? - Mark A. Donohoe
@Joe,已经做到了! :) 但是...我*真的很喜欢SO社区如何在凌晨1点帮助一个人。 :) 几乎像在谷歌上搜寻这些东西一样快,只不过有真正的人来讨论陷阱和注意事项,而不仅仅是链接示例页面可能或者不可能适合。 - Mark A. Donohoe

2
我的想法是,不再从List派生子类,而是创建一个直接使用List内部的类,然后我可以简单地公开自己的Add/Remove方法并在其中处理'Parent'的调整。但是,这样做会破坏枚举内部集合的能力,因为它被封装在类内部。
你仍然可以在自定义集合上实现IEnumerable接口。在类中使用List<>作为内部实现细节,就可以通过接口允许枚举。
class LocationList : IEnumerable<Location>
{
    List<Location> _list; // initialize somewhere

    public IEnumerator<Location> GetEnumerator() 
    {
         return _list.GetEnumerator();
    }

    IEnumerable.IEnumerator GetEnumerator() 
    {
         return this.GetEnumerator();
    }

    // ... your other custom properties and methods
}

1
更好的做法可能是实现IList<T>,它包括IEnumerable<T>,但也允许在可能需要列表或至少集合的上下文中使用它。 - siride
@siride,当然,他可以实现更强大的IList<T>。它带有他可能或可能不想提供的附加功能。重点并不是他必须实现哪个接口。事实上,他根本不需要实现接口(他可以简单地定义一个适当的GetEnumerator方法),只是这样做更符合习惯用法。 - Anthony Pegram
@Anthony,我只是想让你知道,我本来要把这个标记为答案的,但看起来更简单的方法仍然可以满足我们所有的需求,那就是将其从List<T>更改为Collection<T>,这样我就可以重写我需要的内容。不过,知道我可以将枚举代码委托给内部集合真的很酷,所以我投了你的票。我得想一想IList<T>与IEnumerable<T>哪个更好...IList<T>对我们来说可能有些过度,因为我们实际上只对枚举感兴趣,但能够在List<T>可用的地方使用它还是很酷的。 - Mark A. Donohoe

1
尝试使用通用的 BindingList<T>,它会在添加和移除项时触发事件。

请查看CanGen在上面回答中关于ObservableCollection<T>的评论。 - Mark A. Donohoe
@Marque:你应该意识到有一个专门用于添加项目的事件,而集合本身并不会检测更改(只有实现了INotifyPropertyChanged接口的类型才会)。因此,我认为你担心的这种额外开销实际上并不存在。委托调用和虚函数调用几乎完全相同的代价。 - Ben Voigt
实际上,当你在子类中重写 Collection<T>(List<T> 不支持覆盖 add/remove 实现)时,你拦截了消息调用本身,因此基础集合甚至不知道调用已经被发出以引发这样的通知。另外,Collection<T> 没有要触发的事件!这就是 ObservableCollection<T> 的作用。而且,对象本身并不知道集合,更不知道它们是否在其中,所以我必须尊重地不同意你的评估。简单地对 Collection<T> 进行子类化就可以得到我想要的东西。 - Mark A. Donohoe
@Marque:您能够让您的评论保持主题吗?我的答案涉及到了 BindingList<T>,它确实有一个事件。而且 BindingList<T> 明确说明当包含的对象类型实现了 INotifyPropertyChanged 接口时,仅在更改项时会生成 ListChanged 事件。主要是关于事件开销方面您可能有些困惑。然而,像 BindingList<T> 这样的基于事件的设计与虚方法调用一样快。 - Ben Voigt
我相信我在谈论主题。我明确表示您的建议增加了比所需更多的内容,而您回复讨论了子对象的通知和IListPropertyChanged。此外,它包含不正确的信息。您的评论“...集合不会检测更改(只有实现INotifyPropertyChanged的类型才会检测其自身的更改)”实际上是相反的。BindingList<T>会监听其子项的更改,如果它们支持INotifyPropertyChanged,而不是相反。我并不是要成为一个喷子。该网站的服务条款明确指出要指出不正确的信息。 - Mark A. Donohoe
显示剩余5条评论

0
然而,这样做会破坏对内部集合进行枚举的能力,因为它在内部。
只要遵循正确的技术并实现IEnumerable和ICollection,就不是这样。
但我会使用Observable collection而不是重新发明轮子。
当然,您可以使用Reactive扩展来完成所需的操作。

我认为您不需要ICollection。IEnumerable足矣。话虽如此,看起来@Anthony Pegram的答案是处理这种情况的最佳方案……只需委托给内部集合即可。但是,仅将基类从List<T>更改为Collection<T>似乎就可以解决问题,所以我选择这个方案。 - Mark A. Donohoe

0

这对于仅仅暴露内部集合的IEnumerable接口来说有点过度了。如果我选择纯子类,那么对象图就不会对齐。看起来这不是这种用例的适当模式。 - Mark A. Donohoe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接