Michael Anderson
Michael Anderson

Reputation: 73510

Returning an event from a function

What is the syntax to return an event from a function? (Not to call the event, to return it so that it can be bound to functions).

I have a container class that contains a dictionary where each members has an event.

The aim is to be able to write something like this:

Container c = new Container();
c.CreateEventForKey("a");             // Create the member in the dictionary
c.EventForKey("a") += some_function;  // Bind some_function to the event in the "a" member
c.OnEventForKey("a","b");             // Calls some_function with argument "b"

The Container class looks like this:

public class Container {

  public class Member {
     public event Action<string> AnEvent;
     public void OnEvent( string v ) { if(AnEvent!=null) { AnEvent(v); } }
  }

  protected Dictionary<string,Member> members;

  // This seems to work OK.
  public void OnEventForKey(string k, string v) {
    if ( members.ContainsKey(k) ) { members[k].OnEvent(v); }
    else { /* report error */ }
  }

  // Can't get this to compile.
  public event Action<string> EventForKey(string k ) {
    if ( members.ContainsKey(k) ) { return members[k].AnEvent; }
    else { /* report error */ }
  }
}

How can I define EventForKey so that this does what I expect?

Upvotes: 7

Views: 6268

Answers (3)

Sergey Berezovskiy
Sergey Berezovskiy

Reputation: 236248

Why not simply return member and subscribe to it's event?

public IMember MemberForKey(string key) // return IMember
{
    if (!members.ContainsKey(key))
        throw new Exception();

    return members[key];
}

And then subscribe:

Container c = new Container();
c.CreateEventForKey("a");            
c.MemberForKey("a").AnEvent += some_function;
c.OnEventForKey("a", "b"); 

But you have public OnEvent method in Member class. In order to forbid raising events by client, you can create interface which will show only event. Just implement this interface by Member class:

public interface IMember
{
    event Action<string> AnEvent;
} 

And yes, you cannot return event, because actually event is not object, it is set of two methods add and remove, which add and remove delegates to inner field of delegate type. Here is how your event looks like:

  private Action<string> _action; // field of delegate type

  public event Action<string> AnEvent
  { 
      add { _action += value; }
      remove { _action -= value; }
  }

Purpose of event is to provide only two operations for clients - adding and removing handlers. Delegate itself is hidden to clients. You can make it public:

public Action<string> _action;

But in this case any client can invoke it.

UPDATE: if you want to go with Subscribe/Remove syntax, then just use dictionary with handlers:

public class Container
{
    private Dictionary<string, Action<string>> handlers = 
            new Dictionary<string, Action<string>>();

    public void CreateEventForKey(string key)
    {
        // with empty handler added you can avoid null check
        handlers.Add(key, (value) => { });
    }

    public void OnEventForKey(string key, string value)
    {
        if (!handlers.ContainsKey(key))
            throw new Exception();

        handlers[key](value);
    }

    public void Subscribe(string key, Action<string> handler)
    {
        if (!handlers.ContainsKey(key))
            throw new Exception();

        handlers[key] += handler;
    }
}

Upvotes: 2

Ian Newson
Ian Newson

Reputation: 7949

Here's complete working example:

class Program
{
    static void Main(string[] args)
    {
        Container c = new Container();
        c.CreateEventForKey("a");             // Create the member in the dictionary
        c.EventForKey("a").Add(str => Console.WriteLine(str));
        c.EventForKey("a").Add(str => Console.WriteLine(str.ToUpper()));
        c.OnEventForKey("a", "baa baa black sheep");

        Console.ReadLine();
        }
    }

    public class Container
    {

        public class Member
        {
        public List<Action<string>> AnEvent = new List<Action<string>>();
        public void OnEvent(string v)
        {
            if (AnEvent != null)
            {
                this.AnEvent.ForEach(action => action(v));
            }
        }

        public void AddEvent(Action<string> action)
        {
            this.AnEvent.Add(action);
        }
    }

    protected Dictionary<string, Member> members = new Dictionary<string,Member>();

    public void CreateEventForKey(string key)
    {
        this.members[key] = new Member();
    }

    // This seems to work OK.
    public void OnEventForKey(string k, string v)
    {
        if (members.ContainsKey(k)) { members[k].OnEvent(v); }
        else { /* report error */ }
    }

    public List<Action<string>> EventForKey(string k)
    {
        if (members.ContainsKey(k)) { return members[k].AnEvent; }
        else { throw new KeyNotFoundException(); }
    }
}

The difference is to behave similarly to an event by using a list of delegates.

Upvotes: 0

Jon Skeet
Jon Skeet

Reputation: 1501033

What is the syntax to return an event from a function?

You can't, easily. Events - like properties - aren't really first class "objects" as such; they're members of a class. You don't really have a class member here - you're trying to just keep delegates in a dictionary.

You could create your own "event-like" container, but it's probably better to consider alternative designs, e.g.

c.Subscribe("a", SomeFunction);
c.OnEventForKey("a");

You might want to look at EventHandlerList for inspiration.

Upvotes: 9

Related Questions