简体   繁体   中英

How to aggregate events across a collection of identical objects (same class - different instances) through another 'aggregation object'?

I struggled to write a more appropriate 'title' to this question - I will try and clean it up when I have a better understanding of the solution and the correct terminology. :D

There is a pattern that is repeating in the last two projects I've been working on (the projects are both heavily event driven).

The pattern is:

public interface ISourceOfEvents {
    event Action<EventInfo1> SomethingHappened1;
    event Action<EventInfo2> SomethingHappened2;
    {...}
}

public class SingleSourceOfEvents : ISourceOfEvents {
     public event Action<EventInfo1> SomethingHappened1 = delegate { };
     public event Action<EventInfo2> SomethingHappened2 = delegate { };
     {...}
}

public class GroupSourceOfEvents : ISourceOfEvents {

    public IList<ISourceOfEvents> SourceOfEventsGroup { get; private set; }

    public event Action<EventInfo1> SomethingHappened1 = delegate { };
    public event Action<EventInfo2> SomethingHappened2 = delegate { };
    {...}

    public GroupSourceOfEvents(IList<ISourceOfEvents> sourceOfEventsGroup) {

        SourceOfEventsGroup = sourceOfEventsGroup;

        foreach (var s in SourceOfEventsGroup) {

            s.SomethingHappened1 += x => SomethingHappened1(x);
            s.SomethingHappened2 += x => SomethingHappened2(x);
            {...}
        }
    }
}

My question boils down to 'how do I make the constructor for GroupSourceOfEvents map the event bindings automatically?'.

Currently the code is hard to maintain because there are multiple places across the projects that new events need to be added. The ISourceOfEvents interface allows me to add the event to a central place and get compiler errors everywhere I need to add the event but I need a similar solution for the event binder mappings in the GroupSourceOfEvents - or for it to be completely dynamic.

I would like something similar to this:

// PSEUDO-CODE WARNING! :D
// PSEUDO-CODE WARNING! :D
// PSEUDO-CODE WARNING! :D

public GroupSourceOfEvents(IList<ISourceOfEvents> sourceOfEventsGroup) {

    SourceOfEventsGroup = sourceOfEventsGroup;

    var events = typeof(ISourceOfEvents).GetEvents();

    foreach (var s in SourceOfEventsGroup) {

        foreach (var e in events) {
            e.AddEventHandler(s, e.GetRaiseMethod(this));
        }
    }
}

Half my problem is mapping the parameters together. In the currently working code (the top sample) a lambda expression is used but I'm not sure how that can be achieved using the 'style' of technique I'm using in my pseudo code (the bottom sample). The other half of my problem is that GetRaiseMethod(this) is returning MethodInfo opposed to delegate (which is what I would need to use AddEventHandler ) and I'm not sure how to "get at" the delegate .

Any help would be greatly appreciated. I'm also open to different patterns or approaches that may be more standard (if this one is not).

I'm using C# and .Net 4.

I'm not sure if I entirely understand what you're trying to do; from what I understand, you're trying to create an "aggregator" object ( GroupSourceOfEvents ) that contains a list of ISourceOfEvents objects.

Then later, you want to allow subscribers to subscribe to the events exposed by the GroupSourceOfEvents object. Those events are raised whenever any one of the contained ISourceOfEvents objects raise their events.

Below are two solutions that I think might address what you want. These solutions use event accessors (add / remove). The first is a simple example with a number of pitfalls. The second is a more complex example that I think addresses all of the pitfalls of the first example.

public class GroupSourceOfEvents : ISourceOfEvents
{
    public IList<ISourceOfEvents> SourceOfEventsGroup { get; private set; }

    public event Action<EventInfo1> SomethingHappened1
    {
        add
        {
            foreach (var s in SourceOfEventsGroup)
            {
                s.SomethingHappened1 += value;
            }
        }
        remove
        {
            foreach (var s in SourceOfEventsGroup)
            {
                s.SomethingHappened1 -= value;
            }
        }
    }

    public event Action<EventInfo2> SomethingHappened2
    {
        add
        {
            foreach (var s in SourceOfEventsGroup)
            {
                s.SomethingHappened2 += value;
            }
        }
        remove
        {
            foreach (var s in SourceOfEventsGroup)
            {
                s.SomethingHappened2 -= value;
            }
        }
    }
    // {...}

    public GroupSourceOfEvents(IList<ISourceOfEvents> sourceOfEventsGroup)
    {
        SourceOfEventsGroup = sourceOfEventsGroup;

        foreach (var s in SourceOfEventsGroup)
        {
            // {...}
        }
    }
}

The main pitfall that I can think of is that you would have to ensure that all of your ISourceOfEvents objects were added to your GroupSourceOfEvents object before any subscribers begin to subscribe to any of the events. This is because if you add any of the ISourceOfEvents objects from the list, then any of the previously subscribed listeners will not get notified of events on the newly added objects. Similarly, if any subscriber unsubscribes to an event on GroupSourceOfEvents after any of those ISourceOfEvents objects have been removed from the list, then events on those removed objects will not be properly unsubscribed. Another issue is that there is no locking within the events' add/remove accessors, which could be an issue if multithreading is involved.

You could address these pitfalls with a more complicated version of the same approach; you could have a private delegate variable that corresponds to each event, and within the add/remove code for each event you could use the local variable to store all subscribers. Then, you'd have to avoid exposing your SourceOfEventsGroup list publicly and provide some methods to add/remove from the list, so that upon adding you could use your private delegate variables to subscribe to the new object's events, or upon removing you could use the private delegate variables to unsubscribe to the removed object's events.

Below is another version of the above example that attempts to address the issues that I mentioned. Notice that I've exposed the list as IEnumerable<ISourceOfEvents> to avoid publicly exposing the add/remove functionalty of the list. I've added the AddSource method in order to allow a source to be added and to subscribe to that source's events using the subscribers that have already been registered. Similarly, there is a RemoveSource method to handle removing a source from the list and unsubscribing to its events. There are also locks to address potential multithreading issues.

public class GroupSourceOfEvents : ISourceOfEvents
{
    private object SourceOfEventsGroupLock = new object();

    private IList<ISourceOfEvents> _SourceOfEventsGroup;

    public IEnumerable<ISourceOfEvents> SourceOfEventsGroup
    {
        get { return _SourceOfEventsGroup; }
    }

    public void AddSource(ISourceOfEvents source)
    {
        lock (SourceOfEventsGroupLock)
        {
            source.SomethingHappened1 += _SomethingHappened1;
            source.SomethingHappened2 += _SomethingHappened2;
            _SourceOfEventsGroup.Add(source);
        }
    }

    public void RemoveSource(ISourceOfEvents source)
    {
        lock (SourceOfEventsGroupLock)
        {
            source.SomethingHappened1 -= _SomethingHappened1;
            source.SomethingHappened2 -= _SomethingHappened2;
            _SourceOfEventsGroup.Remove(source);
        }
    }

    private Action<EventInfo1> _SomethingHappened1;
    public event Action<EventInfo1> SomethingHappened1
    {
        add
        {
            lock (SourceOfEventsGroupLock)
            {
                _SomethingHappened1 += value;

                foreach (var s in SourceOfEventsGroup)
                {
                    s.SomethingHappened1 += value;
                }
            }
        }
        remove
        {
            lock (SourceOfEventsGroupLock)
            {
                _SomethingHappened1 -= value;

                foreach (var s in SourceOfEventsGroup)
                {
                    s.SomethingHappened1 -= value;
                }
            }
        }
    }

    private Action<EventInfo2> _SomethingHappened2;
    public event Action<EventInfo2> SomethingHappened2
    {
        add
        {
            lock (SourceOfEventsGroupLock)
            {
                _SomethingHappened2 += value;

                foreach (var s in SourceOfEventsGroup)
                {
                    s.SomethingHappened2 += value;
                }
            }
        }
        remove
        {
            lock (SourceOfEventsGroupLock)
            {
                _SomethingHappened2 -= value;

                foreach (var s in SourceOfEventsGroup)
                {
                    s.SomethingHappened2 -= value;
                }
            }
        }
    }
    // {...}

    public GroupSourceOfEvents(IList<ISourceOfEvents> sourceOfEventsGroup)
    {
        _SourceOfEventsGroup = sourceOfEventsGroup;
    }
}

EDIT: I've attempted to clean up my answer by rearranging paragraphs, rewording sentences, and removing an unfinished sentence. The content remains the same.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM