Is there any way to create indexed events in C# (or some workaround)?

Viewed 2018

The caption is confusing. Let me clarify a bit:

I'd like to provide events that depend on a parameter so an observer can decide to receive events if something happens to a specific "id". It could look like this:

public event EventHandler Foo (string id);

I'm aware that this syntax is wrong in .NET 3.5, and I'm also aware that this idea introduces additional problem (for instance, how do we manage unsubscription?).

How should I circumvent this issue? I thought about using something like:

public EventHandler Foo (string id);

which is at least legal syntax and could work, but it still does not look very great to me.

Edit: I'm not asking about passing arguments to the callback function. My idea is more like this:

class Bleh
{
    public event EventHandler Foo (string index);

    private void RaiseEvents() // this is called by a timer or whatever
    {
        Foo["asdf"] (this, EventArgs.Empty); // raises event for all subscribers of Foo with a parameter of "asdf"
        Foo["97"] (this, EventArgs.Empty); // same for all "97"-subscribers
        // above syntax is pure fiction, obviously
    }
}

// subscribe for asdf events via:
Bleh x = new Bleh ();
x.Foo["asdf"] += (s, e) => {};

Explanation
Since you're probably wondering why I try to do this, I'll explain my situation. I've got a class that provides positions of certain objects (each of these identified by some ID string).

Instead of providing an event EventHandler<PositionChangedEventArgs> that is raised for ANY positional changes, I'd like to have an event for every object (accessed by an index), so observers can listen to the events for a specific ID only.

10 Answers

You could do something like this:

public class Foo
{
    public class Bar
    {
        public event EventHandler PositionChanged;

        internal void RaisePositionChanged()
        {
            var handler = PositionChanged;
            if (handler != null)
                handler(this, EventArgs.Empty);
        }
    }

    private Dictionary<string, Bar> m_objects;

    public Bar this[string id]
    {
        get
        {
            if (!m_objects.ContainsKey(id))
                m_objects.Add(id, new Bar());

            return m_objects[id];
        }
    }

    private void RaisePositionChanged(string id)
    {
        Bar bar;
        if (m_objects.TryGetValue(id, out bar))
            bar.RaisePositionChanged();
    }
}

Then to subscribe an event, it would be as simple as this:

Foo foo = new Foo();

foo["anId"].PositionChanged += YourHandler;

You need to use an EventArgs-derived class which includes the ID, and then use EventHandler<IdEventArgs> or whatever:

public class IdEventArgs : EventArgs
{
    private readonly string id;
    public string Id { get { return id; } }

    public IdEventArgs(string id)
    {
        this.id = id;
    }
}

public event Eventhandler<IdEventArgs> Foo;

When you raise the event you'll need to create an instance of IdEventArgs, and then the subscriber can examine that and decide what to do with it.

I've just started using the Rx Framework and it is brilliant. I think it might be what you're looking for.

http://msdn.microsoft.com/en-us/devlabs/ee794896.aspx

Subscription and un-subscription are handled in the framework. It's been called LINQ to events. It is the 'mathematical dual' of IEnumerable.

Cheers, -jc

I think Reactive Extensions for .NET is exactly what you're looking for.

The idea* is this:

First, define a class that derives from EventArgs and includes the information you want (in particular, whatever "index" you had in mind). Something like this:

public class IndexedEventArgs : EventArgs {
    public string Index { get; private set; }

    public IndexedEventArgs(string index) {
        Index = index;
    }

    // ...
}

Next, for the class that will be raising events, implement a single event using EventHandler<TEventArgs> and this class you just defined. Within this class definition, create an object that implements IObservable as follows:

public class ClassWithIndexedEvents {
    public event EventHandler<IndexedEventArgs> IndexedEvent;

    public IObservable Events { get; private set; }

    public ClassWithIndexedEvents() {
        // yeah, this feels a little weird, but it works
        Events = Observable.FromEvent<IndexedEventArgs>(this, "IndexedEvent");
    }

    // ...
}

Now, in your code where you want to subscribe to only events matching a certain index, you can filter your Events property in just the same way you would an IEnumerable:

// code mangled to fit without scrolling
public IDisposable CreateSubscription(
    string eventIndex,
    Action<IEvent<IndexedEventArgs>> handler) {

    return Events.Where(e => e.Index == eventIndex).Subscribe(handler);
}

Notice that the Subscribe method returns an IDisposable object; this is your key to later unsubscribing from the filtered event you just subscribed to. The code is pretty obvious:

var fooSubscription = objectWithIndexedEvents.CreateSubscription(
    "foo",
    e => DoSomething(e)
);

// elsewhere in your code
fooSubscription.Dispose();

* Disclaimer: I am writing all of this code more or less from memory of how Rx works; I haven't tested it, since I don't have Rx installed on the machine I'm currently using. I can check tomorrow on a different machine to make sure it's all written correctly; for now it should at least serve as an illustration to give you an idea how Rx works. To learn more you can always look up Rx tutorials online.

I don't know if I'd use events in this case, but I'm not sure that's really the biggest issue.

If you're trying to control subscribers, I think you're better off letting the subscribers handle the filtering. Only they know what they really want to filter on, so putting the filtering code in the class the emits the events seems sub-optimal.

Let me try to clarify a bit, if I can... The code to determine whether recipient A cares about an event from emitter B lives somewhere. It might seem to make sense to put it in B. However, the problem comes when you realize that you have to consider recipients C, D, and E as well. They may have complicated logic to determine what they care about (and it may even change over itme). Putting all of this logic in our emitter (B) is going to make a large, clumsy class that's hard to use.

Another option is to have A have the logic on whether or not it wants the event internally. This localizes the logic to A, keeps B clean and easy to consume by everyone else. However, the downside of this is that the subscription logic isn't usable by C if it happens to be the same.

But if we really think about it, we have three things happening here... an event being emitted, filtering of events to recipients, and receipt/reaction to the events. The Single Responsibility Principle tells us that a class should only have on responsibility - one reason to change. By including the filtering logic in either A or B, whichever one gets it now has two responsibilities, and two reasons to change.

So, what I'd be tempted to do in this case is create another class, Q, that contains the logic for event filtering. Now, neither A or B get the extra logic in their code. C doesn't have to re-implement it. And, as a bonus, we can now easily tie multiple filters together to get complex filter behavior based out of very simple components.

I´ve prepared a complete example. You can use it in this way:

        eventsSubscriptions["1"].EventHandler = new EventHandler(this.Method1);
        eventsSubscriptions["2"].EventHandler = new EventHandler(this.Method2);
        eventsSubscriptions["3"].EventHandler = new EventHandler(this.Method3);

        Boss Boss1 = new Boss("John Smith");
        Boss Boss2 = new Boss("Cynthia Jameson");

        Employed Employed1  = new Employed("David Ryle");
        Employed Employed2 = new Employed("Samantha Sun");
        Employed Employed3 = new Employed("Dick Banshee");

        // Subscribe objects to Method 1
        eventsSubscriptions["1"].Subscribe(Boss1);
        eventsSubscriptions["1"].Subscribe(Employed1);

        // Subscribe objects to Method 2
        eventsSubscriptions["2"].Subscribe(Boss2);
        eventsSubscriptions["2"].Subscribe(Employed2);

        // Subscribe objects to Method 3
        eventsSubscriptions["3"].Subscribe(Employed3);

Then, you can call RaiseAllEvents() methods, and this is the console output:

  • Method 1 raised with Boss John Smith
  • Method 1 raised with Employee David Ryle
  • Method 2 raised with Boss Cynthia Jameson
  • Method 2 raised with Employee Samantha Sun
  • Method 3 raised with Employee Dick Banshee

In the following lines, I´ll paste the code of all the classes involved. With a little patience and copy/paste, you´ll be able to test it =P Hope it helps you.

--- The Code ---

Main

namespace MyExample
{
    public class Program
    {

        static void Main(string[] args)
        {
            SomeExampleClass someExampleInstance = new SomeExampleClass();

            someExampleInstance.SuscribeObjects();            
            someExampleInstance.RaiseAllEvents();            

            Console.ReadLine();
        }


    }
}

Class Person

namespace MyExample
{
    public abstract class Person
    {
        protected string name;

        public Person(string name)
        {
            this.name = name;
        }

        public string Name
        {
            get
            {
                return name;
            }
            set
            {
                name = value;
            }
        }

        public override string ToString()
        {
            return (this.GetType().Name + " " + name);
        }
    }
}

Class Boss

namespace MyExample
{
    public class Boss : Person
    {
        public Boss(string name)
            : base(name)
        { }
    }
}

Employee

namespace MyExample
{
    public class Employee : Person
    {
        public Employee(string name)
            : base(name)
        { }
    }
}

Class SomeExampleClass

namespace MyExample
{
    public class SomeExampleClass
    {

        private EventsSubscriptions eventsSubscriptions = new EventsSubscriptions();

        private void Method1(object sender, System.EventArgs e)
        {
            Console.WriteLine("Method 1 raised with " + sender.ToString());
        }

        private void Method2(object sender, System.EventArgs e)
        {
            Console.WriteLine("Method 2 raised with " + sender.ToString());
        }

        private void Method3(object sender, System.EventArgs e)
        {
            Console.WriteLine("Method 3 raised with " + sender.ToString());
        }

        public void SuscribeObjects()
        {            
            eventsSubscriptions["1"].EventHandler = new EventHandler(this.Method1);
            eventsSubscriptions["2"].EventHandler = new EventHandler(this.Method2);
            eventsSubscriptions["3"].EventHandler = new EventHandler(this.Method3);

            Boss Boss1 = new Boss("John Smith");
            Boss Boss2 = new Boss("Cynthia Jameson");

            Employee Employee1  = new Employee("David Ryle");
            Employee Employee2 = new Employee("Samantha Sun");
            Employee Employee3 = new Employee("Dick Banshee");

            // Method 1
            eventsSubscriptions["1"].Subscribe(Boss1);
            eventsSubscriptions["1"].Subscribe(Employee1);

            //// Method 2
            eventsSubscriptions["2"].Subscribe(Boss2);
            eventsSubscriptions["2"].Subscribe(Employee2);

            //// Method 3
            eventsSubscriptions["3"].Subscribe(Employee3);

        }

        public void RaiseAllEvents()
        {
            eventsSubscriptions.RaiseAllEvents();
        }

    }
}

Class EventsSubscriptions

namespace MyExample
{
    public class EventsSubscriptions
    {
        private Dictionary<string, Subscription> subscriptions = new Dictionary<string, Subscription>();

        public Subscription this[string id]
        {
            get
            {
                Subscription subscription = null;

                subscriptions.TryGetValue(id, out subscription);

                if (subscription == null)
                {                    
                    subscription = new Subscription();
                    subscriptions.Add(id, subscription);
                }

                return subscription;

            }
        }

        public void RaiseAllEvents()
        {
            foreach (Subscription subscription in subscriptions.Values)
            {
                Subscription iterator = subscription;

                while (iterator != null)
                {
                    iterator.RaiseEvent();
                    iterator = iterator.NextSubscription;
                }
            }
        }


    }
}

Class Subscription

namespace MyExample
{
    public class Subscription
    {
        private object suscribedObject;
        private EventHandler eventHandler;
        private Subscription nextSubscription;

        public object SuscribedObject
        {
            set
            {
                suscribedObject = value;
            }
        }

        public EventHandler EventHandler
        {
            set
            {
                eventHandler = value;
            }
        }

        public Subscription NextSubscription
        {
            get
            {
                return nextSubscription;
            }
            set
            {
                nextSubscription = value;
            }
        }

        public void Subscribe(object obj)
        {

            if (suscribedObject == null)
            {
                suscribedObject = obj;
            }
            else
            {
                if (nextSubscription != null)
                {
                    nextSubscription.Subscribe(obj);
                }
                else
                {
                    Subscription newSubscription = new Subscription();
                    newSubscription.eventHandler = this.eventHandler;
                    nextSubscription = newSubscription;
                    newSubscription.Subscribe(obj);
                }
            }
        }

        public void RaiseEvent()
        {
            if (eventHandler != null)
            {
                eventHandler(suscribedObject, new System.EventArgs());
            }
        }
    }
}

I found basically one more or less elegant way to solve this problem:

Use a dictionary of ids to events. Access to add/remove listeners via methods.

// ignore threadsafety and performance issues for now.
private Dictionary<string, EventHandler> _Events = new Dictionary<string, EventHandler> ();

private void AddId (string id)
{
    _Events[id] = delegate {
    };
}

public void Subscribe (string id, EventHandler handler)
{
    _Events[id] += handler;
}

public void Unsubscribe (string id, EventHandler handler)
{
    _Events[id] -= handler;
}

private void Raise (string id)
{
    _Events[id] (this, new EventArgs ());
}

static void Main (string[] args)
{
    var p = new Program ();

    p.AddId ("foo");
    p.Subscribe ("foo", (s, e) => Console.WriteLine ("foo"));
    p.Raise ("foo");

    p.AddId ("bar");
    p.Subscribe ("bar", (s, e) => Console.WriteLine ("bar 1"));
    p.Subscribe ("bar", (s, e) => Console.WriteLine ("bar 2"));
    p.Raise ("bar");

    Console.ReadKey ();
}

You mean something like

public class EventArgs<T> : EventArgs
{
    private T _value;
    public T Value
    {
        get { return this._value; }
        protected set { this._value = value; }
    }

    public EventArgs(T value)
    {
        this.Value = value;
    }
}


// ...

public event EventHandler<EventArgs<string>> Foo;

?

Implemented as a single class, with a simple API.

// subscribe to an event
eventsource.AddHandler( "foo", MyEventHandler );

// unsubscribe
eventsource.RemoveHandler( "foo", MyEventHandler );

// raise event for id
eventsource.RaiseEvent( "foo" );

public class EventSource
{
    Dictionary<string,List<EventHandler>> handlers = new Dictionary<string,List<EventHandler>>();

    public void AddHandler( string id, EventHandler handler )
    {
        if (!handlers.ContainsKey( id )) {
            handlers[id] = new List<EventHandler>();
        }
        handlers[id].Add( handler );
    }

    public void RemoveHandler( string id, EventHandler handler )
    {
        if (handlers.ContainsKey( id )) {
            handlers[id].Remove( handler );
        }
    }

    public void RaiseEvent( string id )
    {
        if (handlers.ContainsKey( id )) {
            foreach( var h in handlers[id] ) {
                h( this, EventArgs.Empty );
            }
        }       
    }
}

How about implementing INotifyPropertyChanged instead?

And then...

protected void NotifyPropertyChanged(String propertyName)
{
    if (PropertyChanged != null)
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}


private void OnSourcePropertyChanged(Object sender, PropertyChangedEventArgs eventArgs)
{
    if (eventArgs.PropertyName == "InterestingName")
    {
        // TODO:
    }
}
Related