How to get only the farthest child to receive a joint event?

I am facing a design problem with my code. I have an object that creates a child object (then the child can create another child, etc.) and both objects subscribe to the same event. But I want most of the child objects to receive the event.

My project overview: I am building an IVR system. When a user calls into the system, the user will have an X-menu choice. Based on what the user selects, they will have a selection submenu, etc. Etc. I use State Machines for this. Every government apparatus needs to "listen" when a user presses a number on their phone. But only the current State Machine has to process the entered number. Each State Machine can create a new State Machine to represent submenus.

Here's some sample code:

Base class:

public delegate void DoSomething(object sender, EventArgs data);
public class Base
{
    public event DoSomething myEvent;
    private IObject foo;

    public Base ()
    {
        foo = new myObjectA(this);
    }

    public void SomeAction()
    {
        ((myObjectA)foo).CreateChild();
    }

    public void EventFired()
    {
        if (myEvent != null)
        {
            myEvent(this, new EventArgs());
        }
    }
}

      

Objecta:

class myObjectA : IObject
{
    private Base theCallingObject;
    private IObject child;
    public myObjectA (Base _base)
    {
        theCallingObject = _base;
        theCallingObject.myEvent += new DoSomething(theCallingObject_myEvent);
    }

    public void CreateChild()
    {
        child = new myObjectB(theCallingObject);
    }

    void theCallingObject_myEvent(object sender, EventArgs data)
    {
        // Handle event
        MessageBox.Show("myObjectA");
    }
}

      

ObjectB:

class myObjectB : IObject
{
    private Base theCallingObject;
    public myObjectB (Base _base)
    {
        theCallingObject = _base;
        theCallingObject.myEvent += new DoSomething(theCallingObject_myEvent);
    }

    void theCallingObject_myEvent(object sender, EventArgs data)
    {
        // Handle event
        MessageBox.Show("myObjectB");
    }
}

      

Now when I do this:

Base blah = new Base();
blah.SomeAction();
blah.EventFired();

      

I am getting message boxes for both A and B. I need to implement Base so that only myObjectB event will receive the event. I will have hundreds of myObject, so I need an implementation at the base level, not at the myObject level. Also, handling it at the myObject level still requires the event to be fired, causing performance issues if there are hundreds of objects.

One solution I looked at was when myObjectA creates a child, unsubscribes from the event, and then re-subscribes when we go back to the myObjectA level. However, I feel that something better could be done.

Does anyone have any idea?

Edit: Using payo input, I ended up with this:

public delegate void DoSomething(object sender, EventArgs data);
public class Base
{
    private IObject foo;
    private List<DoSomething> _myEventStorage;

    public event DoSomething myEvent
    {
        add
        {
            _myEventStorage.Insert(0, value);
        }
        remove
        {
            _myEventStorage.Remove(value);
        }
    }

    public Base ()
    {
        _myEventStorage = new List<DoSomething>();
        foo = new myObjectA(this);
    }

    public void SomeAction()
    {
        ((myObjectA)foo).CreateChild();
    }

    public void EventFired()
    {
        _myEventStorage[0].Invoke(this, new EventArgs());
    }
}

      

+3


source to share


2 answers


For events, each subscriber is queued (placed at the end of the list), FIFO model. You want the most-child object to be a "native" event, not just subscribed and part of an abstract list of other unknown objects.

I would introduce a new model that represents what you are trying to do. This could be what Jason recommended: (he posted his answer as I was typing this)

public class Base
{
  private DoSomething _myEventStorage;
  public event DoSomething myEvent
  {
    add
    {
      _myEventStorage = value;
    }
    remove
    {
      _myEventStorage -= value;
    }
  }
...
  public void EventFired()
  {
    if (_myEventStorage != null)
    {
      _myEventStorage(this, new ChainEventArgs());
    }
  }
}

      

This is the last time. Another option (add to this custom add / remove) is to provide derived EventArgs:

public class ChainEventArgs : EventArgs
{
  public bool Handled { get; set; }
}
public delegate void DoSomething(object sender, ChainEventArgs data);

...

  public event DoSomething myEvent
  {
    add
    {
      var temp = _myEventStorage;
      _myEventStorage = null;
      _myEventStorage += value;
      _myEventStorage += temp; // now all are called, but FILO
    }
    remove
    {
      _myEventStorage -= value;
    }
  }

      



At this point, you can either check Handled

on each IObject

void theCallingObject_myEvent(object sender, ChainEventArgs data)
{
  if (data.Handled)
    return;

  if (I_want_to_block_parents)
    data.Handled = true;
  // else leave it false
}

      

Or add some complexity to your base class and stop naming the chaining (don't let the kids need to check Handled

). I'll show the solution using List <> delegates, but some actions and calls to MulticaseDelegate can do the same. I just feel like the List <> code could be more readable / maintainable.

public class Base
{
  private List<DoSomething> _myEventStorage;
  public event DoSomething myEvent
  {
    add
    {
      _myEventStorage.Insert(0, value);
    }
    remove
    {
      _myEventStorage.Remove(value);
    }
  }
...
  public void EventFired()
  {
    var args = new ChainEventArgs();
    foreach (var handler in _myEventStorage)
    {
      handler(this, args);
      if (args.Handled)
        break;
    }
  }
}

      

+1


source


you will need to explicitly implement myEvent handlers (add / remove) and keep track of the "furthest" regardless of registered observers. then you can send a notification to this single instance.



+2


source







All Articles