How can I detect adds to a generic list in C# 4.0?
Asked Answered
M

6

8

I have a subclass of List<Location> called LocationList. This is a convenient way for us to add other properties (like IsExpanded and such that we can use in the UI. Good enough. But now, we want each location to be aware of its parent. As such, we need to be notified when something is added to LocationList, but you can't override Add and Remove. While we can use ObservableCollection, that's more of a View/ViewModel construct. This is pure model data.

Sure we could manually set the parent when adding it to the collection, but I hate non-automated logic like that as there's nothing to enforce its set correctly. Letting the collection (well, List) automatically say 'Hey... you're being added, so I'll set your parent to the class that owns me." is what I'm after.

My thought is to instead of subclass List<Location> to instead just create a straight class that uses a List<Location> internally, and then I can simply expose my own Add/Remove methods and handle the adjustments to 'Parent' in there. However, doing so breaks being able to enumerate over the internal collection since it's inside.

That said, is there any way to either listen to changes to List<Location>, or at least delegate its IEnumerable interface from the wrapped object to its wrapper?

Madonna answered 7/7, 2011 at 4:33 Comment(0)
S
9

Change your LocationList to inherit from Collection<Location> instead. I don't know why List<T> isn't sealed, but it's not extendable.

Source: Framework Design Guidelines, Second Edition, Page 251:

List<T> is optimized for performance and power at the cost of cleanness of the APIs and flexibility.

Sixtyfourmo answered 7/7, 2011 at 4:36 Comment(1)
This seems like the easiest to implement... just changing List<T> to Collection<T> which doesn't add all the overhead of ObservableCollection<T> although you lose a little performance compared to List<T>. Still, that's more than good enough so you're getting the vote. Thx!Madonna
F
4

List<T> has protected members InsertItem<T>, RemoveItem<T> etc that you can override in your derived class to do what you want.

** UPDATE **

Actually the above is incorrect, it's Collection<T> that has these protected methods. In general, when deriving custom List classes, it is recommended to derive from Collection<T> rather than List<T>.

See this answer.

Fortunna answered 7/7, 2011 at 4:35 Comment(6)
If you're going to override InsertItem/RemoveItem, why don't you just use a version where the work is already done (ObservableCollection)Jessi
ObservableCollection is really optimized more for the ViewModel than the model. The only person that needs to be aware of the change is the collection itself. You don't need the full change-notification overhead.Madonna
@MarqueIV: " I can't even accept your answer for another eleven minutes" - just as well as the initial answer was wrong - too quick off the mark. See update and accept Michael Stum's answer!Fortunna
@Joe, I may have spoken too soon. I'm not seeing either of those items come up in the intellisense for List<T>, nor do I see it in the docs. Are you sure that's the right class?(msdn.microsoft.com/en-us/library/6sh2ey19.aspx)Madonna
@Joe, I see those on Collection<T>, not List<T>. Was that what you meant?Madonna
@Joe, already had! :) Still... DA*N I love how fast the SO community can help a guy out at 1 AM. :) Almost as fast as googling this stuff, except with real people to discuss pitfalls and caveats instead of just linked example pages that may or may not fit.Madonna
M
2

My thought is to instead of subclass List to instead just create a straight class that uses a List internally, and then I can simply expose my own Add/Remove methods and handle the adjustments to 'Parent' in there. However, doing so breaks being able to enumerate over the internal collection since it's inside.

You can still implement IEnumerable<Location> on your custom collection. Use the List<> as the internal implementation detail of the class, and you can allow enumeration via the interface.

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
}
Monodic answered 7/7, 2011 at 4:40 Comment(3)
Better might be to implement IList<T>, which includes IEnumerable<T>, but also allows it to be used in contexts that might want a list, or at the very least, a collection.Brookins
@siride, sure, he can implement the beefier IList<T>. It comes with additional functionality he may or may not want to provide. The point was not specifically what interface he had to implement. In truth, he doesn't have to implement the interface at all (he can simply define an appropriate GetEnumerator method), it's simply more idiomatic to do so.Monodic
@Anthony, just letting you know I was about to mark this as the answer, but it looks like the simpler thing to do with still meeting all of our needs is just to change it from List<T> to Collection<T> which lets me override what I need to. Still, really cool to know I can just delegate the enumeration code down to the internal collection so I voted yours up. I'll have to think if IList<T> vs IEnumerable<T> is better... IList<T> may be overkill for us since we're really only interested in the enumerations for the most part, but being able to use it where List<T> is used is cool.Madonna
E
1

Try the generic BindingList<T>, which raises events when items are added and removed.

Exposition answered 7/7, 2011 at 4:42 Comment(10)
See my comments regarding ObservableCollection<T> in CanGen's answer above.Madonna
@Marque: You do realize that there's an event dedicated to items being added, and that the collection doesn't detect changes (the items themselves do, and only types that implement INotifyPropertyChanged). So I think this extra overhead you're worried about doesn't actually exist. Delegate invocation and virtual function call are almost exactly the same cost.Exposition
Actually, when you override Collection<T> in a subclass (List<T> doesn't support overriding the add/remove implementation), you're intercepting the message call itself so the base collection doesn't yet even know the call was made to raise such notifications. Plus, Collection<T> doesn't have events to raise! That's what ObservableCollection<T> is for. And no, the objects themselves don't know about the collections let alone whether they're in them or not, so I have to respectfully disagree with your assessment. Simply subclassing Collection<T> gives me what I'm after.Madonna
@Marque: Could you keep your comments on topic? My answer talks about BindingList<T>, which does have an event. And the BindingList<T> clearly states that the ListChanged event is generated for item modifications only when INotifyPropertyChanged interface is implemented by the contained object type. Mainly you're confused about the overhead of events, though. An event-driven design such as BindingList<T> is equally fast as a virtual method call.Exposition
I believe I am on topic. I specifically said your suggestion adds much more than needed and you responded discussing notifications and IListPropertyChanged of the child objects. Plus, it contained incorrect information. Your comment "...the collection doesn't detect changes (the items themselves do, and only types that implement INotifyPropertyChanged)." is actually backwards. BindingList<T> listens to its children's changes if they support INotifyPropertyChanged, not the other way around. Not trying to be a troll. The site's ToS specifically say to call out incorrect information.Madonna
one other comment re: yours "Delegate invocation and virtual function call are almost exactly the same cost". I think it's you missing the point. Even if BindingList<T> doesn't do anything with the notifications, it's still being notified of every change to properties of its contained items, none of which our collection needs to be aware of. So it's not delegate-vs-virtual, it's more a call, even if empty, vs no call at all. And again, if it's listening to INotifyPropertyChanged of its children, that can be a lot of calls, especially if that collection gets large.Madonna
@MarqueIV: Does Location implement INotifyPropertyChanged? If not, there are no calls for changes within the children. Remember that listening is not an action, raising an event is the action. Children that track property changes raise an event, which BindingList<T> passes along. Children that don't track property changes cause no action, and the BindingList<T> doesn't either. As far as your claim "the objects don't know about the collections", actually they do, the collection uses the events of the INotifyPropertyChanged interface to tell the object about itself.Exposition
And when the type doesn't implement INotifyPropertyChanged, nothing happens. For your particular case, Collection<Location> is a good choice because you need to add new members to the type anyway. If you didn't need new members, BindingList would allow you to process items being added and removed, without needing to implement a new subclass.Exposition
No, Location doesn't. Good point there. It's purely data. But if it did, that would be something extra passed along to BindingList that we don't need. Again, the only thing that we do need is for the collection to know something was inserted or removed from itself, which a subclass of Collection<T> does. I still disagree that the items have any knowledge whatsoever about their containing collections outside of a listener to the item's INotifyPropertyChanged events, but a) that isn't the case here, and b) that has nothing to do with collections, but rather change notification.Madonna
@MarqueIV: Correct, and that's all the child items need to generate the change notifications is the listener.Exposition
A
0

However, doing so breaks being able to enumerate over the internal collection since it's inside.

This is not true assuming you follow proper technique and implement IEnumerable and ICollection

But I would use Observable collection instead of reinventing the wheel.

And of course you can use Reactive extensions to do what you need.

Airframe answered 7/7, 2011 at 4:38 Comment(1)
I don't think you need ICollection. I think IEnumerable is enough. That said, it looks like @Anthony Pegram's answer is the one that handles that case... just delegate to the internal collection. However, just changing the base class from List<T> to Collection<T> seems to do the trick so that's the one I'm marking.Madonna
M
0

You can either using Observer pattern

Michi answered 7/7, 2011 at 4:40 Comment(1)
That's a bit overkill for simply exposing the IEnumerable interface of an internal collection. If I went with the pure subclass, then the object graph doesn't align. Doesn't look like that's the appropriate pattern for this use-case.Madonna

© 2022 - 2024 — McMap. All rights reserved.