Forwarding events in C#

C#EventsEvent HandlingC# 2.0

C# Problem Overview


I'm using a class that forwards events in C#. I was wondering if there's a way of doing it that requires less code overhead.

Here's an example of what I have so far.

class A
{
   public event EventType EventA;
}

class B
{
   A m_A = new A();
   public event EventType EventB;

   public B()
   {
      m_A.EventA += OnEventA;
   }

   public void OnEventA()
   {
      if( EventB )
      {
         EventB();
      }
   }
}

Class A raises the original event. Class B forwards it as EventB (which is essentially the same event). Class A is hidden from other modules so they can't subscribe to EventA directly.

What I'm trying to do is reduce the code overhead in class B for forwarding the event, as typically there's no real handling of the events in class B. Also I'll have several different events so it would require writing a lot of OnEvent() methods in class B that only serve to forward the events.

Is it possible to automatically link EventA to EventB in some way, so I'd have something like this:

class B
{
   A m_A = new A();
   public event EventType EventB;

   public B()
   {
      m_A.EventA += EventB; // EventA automatically raises EventB.
   }
}

I'm using a C# 2.0 compiler btw.

C# Solutions


Solution 1 - C#

Absolutely:

class B
{
    private A m_a = new A();
    
    public event EventType EventB
    {
        add { m_a.EventA += value; }
        remove { m_a.EventA -= value; }
    }
}

In other words, the EventB subscription/unsubscription code just passes the subscription/unsubscription requests on to EventA.

Note that this doesn't allow you to raise the event just for subscribers who subscribed to EventB, however. It's like passing someone's address directly onto a mass marketing company, whereas your original way is more like subscribing to the mass marketing company yourself, and allowing people to ask you to send copies of the mails to them.

Solution 2 - C#

IMO, your original code is (more or less) correct. In particular, it allows you to provide the correct sender (which should be the B instance for people who think they are subscribing to an event on B).

There are some tricks to reduce the overheads at runtime if the event isn't subscribed, but this adds more code:

class B {
   A m_A = new A();
   private EventType eventB;
   public event EventType EventB {
       add { // only subscribe when we have a subscriber ourselves
           bool first = eventB == null;
           eventB += value;
           if(first && eventB != null) m_A.EventA += OnEventB;
       }
       remove { // unsubscribe if we have no more subscribers
           eventB -= value;
           if(eventB == null) m_A.EventA -= OnEventB;
       }
   }

   protected void OnEventB(object sender, EventArgsType args) {
      eventB?.Invoke(this, args); // note "this", not "sender"
      
   }
}

Solution 3 - C#

This is what I came up with:

public interface IExampleConnection
{
    event ReceivedDataEventHandler ReceivedData;
}

public class ConnectionProxy: IExampleConnection
{
    private IExampleConnection _innerConnection;

    // dictionary to store the original event handler and the closure around it with our own handling logic
    private IDictionary<ReceivedDataEventHandler, ReceivedDataEventHandler> _receivedData = new Dictionary<ReceivedDataEventHandler, ReceivedDataEventHandler>();

    // helps protect access to the dictionary containing the event handlers 
    private object objectLock = new object();

    public ConnectionProxy(IExampleConnection innerConnection)
    {
        _innerConnection = innerConnection;
    }
    
    public event ReceivedDataEventHandler ReceivedData
    {
        add
        {
            lock (objectLock)
            {
                // use the original event handler `value` as a key in the dictionary
                // our custom handler becomes the value
                _receivedData.Add(value, (sender, args) =>
                {
                    // insert logic that you want to run before the original event handler

                    // call the original event handler
                    value(sender, args);

                    // insert logic that you want to run after the original event handler finishes

                });
                // add our handler to the dictionary by using the value as the key
                _innerConnection.ReceivedData += _receivedData[value];
            }
        }
        remove
        {
            lock (objectLock)
            {
                // use the incoming event handler `value` to lookup our wrapper around it
                _innerConnection.ReceivedData -= _receivedData[value];
            }
        }
    }
}

It's a bit more code than I would normally like. I suspect there's way to make it more concise, but it worked well enough for my purposes.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionuser3891View Question on Stackoverflow
Solution 1 - C#Jon SkeetView Answer on Stackoverflow
Solution 2 - C#Marc GravellView Answer on Stackoverflow
Solution 3 - C#M. Scott FordView Answer on Stackoverflow