views:

32

answers:

1

Of Note: This is more of a curiosity question than anything else.

Given a List<Window> where each window has an event attached to the Close Event which removes the window from the collection, how could you use delegates / events to defer the execution of the Close Event until the collection has been iterated?

For example:

public class Foo
{

    private List<Window> OpenedWindows { get; set; }

    public Foo()
    {
        OpenedWindows = new List<Window>();
    }

    public void AddWindow( Window win )
    {
        win.Closed += OnWindowClosed;
        OpenedWindows.Add( win );
    }

    void OnWindowClosed( object sender, EventArgs e )
    {
        var win = sender as Window;

        if( win != null )
        {
            OpenedWindows.Remove( win );
        }
    }

    void CloseAllWindows()
    {
        // obviously will not work because we can't 
        // remove items as we iterate the collection 
        // (the close event removes the window from the collection)
        OpenedWindows.ForEach( x => x.Close() );

        // works fine, but would like to know how to do
        // this with delegates / events.
        while( OpenedWindows.Any() )
        {
            OpenedWindows[0].Close();
        } 
    }

}

Specifically, within the CloseAllWindows() method, how could you iterate the collection to call the close event, but defer the event being raised until the collection has been completely iterated?

A: 

Presumably you're trying to avoid the "Collection was modified" exception in the first case.

Really the only way to "defer" this is to make a copy of the collection, which involves iterating the original collection fully and adding the items to a new collection:

var openedWindowsCopy = new List<Window>(OpenedWindows);
foreach (var window in openedWindowsCopy)
    window.Close();

Also, your second method is valid, although typically when you need to clean up instances in an indexed collection such as a List<T>, you do it simply by counting down:

for (int i = OpenedWindows.Count - 1; i >= 0; i--)
    OpenedWindows[i].Close();

Personally, I see no reason to complicate the issue with more delegates.

Aaronaught
Thank you for the answer. I realize I'm complicating the issue, but I wanted to see if there was another way. I guess I should have used / added the example of copying the list to a new list as well but I thought the question was pretty clear that I was looking for the complicated version ;)
Metro Smurf
@Metro: This copying version meets your spec exactly. It iterates through the entire collection, but does not close any windows until the entire collection has been iterated. Any implementation that tries to "defer" execution of the `Close` methods would have to save all of the iterated elements so that it could execute them later; this is exactly the same as making a copy. You could always write an extension method to encapsulate the copy-and-run semantic, but it's going to be less efficient than the second, indexed version in this answer.
Aaronaught
I realize I'm going down the rabbit hole here ;) Like I said, this was more out of curiosity than anything else. In other words, regardless of best practices, how would you do it the complicated way? Your answer is appreciated and I understand what you're saying. Now, ready to go down the rabbit hole with me?
Metro Smurf
@Metro: I really don't understand what you're getting at. Write an extension method that accepts an `Action<T>`, iterates through the sequence, saves each element in a list or on a stack, and then executes the action on the saved elements. Basically identical to the code at the top, except inside a method. Maybe if you could explain what you think is supposed to happen / should be different, I can be more detailed.
Aaronaught
@Aaaron - apologies for the quite delayed response. Regardless of how inane it may be, is there even a way to push the close action into a delegate while inside the ForEach loop, but not invoke the delegate until after the loop is completes?
Metro Smurf