tags:

views:

386

answers:

10

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.

+8  A: 

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.

Jon Skeet
I think you're talking about something different. I edited the question to clarify.
mafutrct
A: 

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;

?

herzmeister der welten
Nope :) See updated question.
mafutrct
Yes, RX is what you're looking for then, as MisterJames mentioned.
herzmeister der welten
+5  A: 

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

MisterJames
Could you add a short example on how to use Rx so I can understand how it fits my needs?
mafutrct
@mafutrct: I've written up an answer with some example code to illustrate one way that Rx can be used to accomplish what you're going for. @MisterJames: It's possible I made some mistakes in my examples. Feel free to correct me if that's the case.
Dan Tao
A: 

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 ();
}
mafutrct
You could also use a Dictionary<string, EventHandler> and use += and -= to subscribe and unsubscribe. If the id is unique, you can just set the delegate instead of chaining.
Kai Wang
Indeed, nice, this works very well. Actually I tried this earlier but it did not work, I must have messed up. Thanks!
mafutrct
+12  A: 

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;
SelflessCoder
Your `Bar` class would need an `internal void RaiseEvents()` method in order to work.
Gabe
Indeed, I updated the answer.
SelflessCoder
Nice idea, not much additional code but very clean syntax for subscribers.
mafutrct
+1 I agree with you. This is the answer in what I was thinking while I was reading the question.
Javier Morillo
+1, basically exactly how I was thinking of it.
Alastair Pitts
+2  A: 

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.

kyoryu
In this case, the filtering is really simple: Every subscriber wants to receive events of only 1 object, identified by its id. There really is not more filtering logic, and the code required to create a subscription should be a one-liner, so this should not be much of an issue.
mafutrct
@mafutrct: Right now, maybe, but I've often found that assumptions I make at one point end up being very wrong later on. At any rate, I'm not even talking about (really) adding any additional code - just whether the resonsibility of 'does this recipient care about this mesage' belongs in the recipient, or the sender. It's a one-liner, true, but it's a one-liner in either class.
kyoryu
A: 

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 );
            }
        }       
    }
}
Lachlan Roche
Yes, but see http://stackoverflow.com/questions/2237927/is-there-any-way-to-create-indexed-events-in-c-or-some-workaround/2244501#2244501 for a simpler version.
mafutrct
+4  A: 

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.

Dan Tao
+1 Interesting. It's a tad bit sad that 3rd party has to be used.
mafutrct
@mafutrct: Ha, I suppose that's one way of looking at it. Considering it's a Microsoft project, though, I don't really view it as "3rd party" any more than the .NET framework itself is 3rd party (which it **is**, of course, but I've never thought, "It's a tad bit sad I have to use this 3rd party '.NET' thing for this project..."). You could of course write your own library that mimics what Rx does, but why would you?
Dan Tao
Yes, of course, it just feels better to use less libs to me. Doing things in an easy and effective way in "native code" is obviously always the best option - _if_ it is possible.
mafutrct
hrm...not sure what you mean? by 'native code', are you referring to only what's in System? Rx will be baked in to the next release. extra libs aren't really a concern, imho, but please explain if i'm missing your point! if you go about having to write your own classes every time you need to subscribe to events you'll have a lot harder time (and more testing to do each time) you want something like this. this is a one-liner to meet your needs: Events.Where(e => e.Index == eventIndex).Subscribe(handler); head over to channel 9 and watch the vids...WELL worth it.
MisterJames
+2  A: 

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());
            }
        }
    }
}
Javier Morillo
+1 nice example, but the design somehow feels weird (compared to Jeff's idea).
mafutrct
Thanks @mafutrct. I know that my example is large, but see that it´s very flexible. You can subscribe objects to all the methods you desire, not only to PositionChanged. =)
Javier Morillo
A: 

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:
    }
}
Danny Varod
That's basically the same as JonSkeet already proposed. It does solve the problem more or less, but it is not the way it ought to be - I'd like to filter events at the source.
mafutrct
For optimal implementations, you either need a dictionary at the source, or at the targets, if you keep the dictionary at the targets it enables connecting one handler to a few different events when required.
Danny Varod