Best way to connect to events far down in a callstack in C #?

What is the best design decision for a “top-level” class to join an event for a class that could be “5+ layers in a freeze frame”?

For example, perhaps MainForm spawned an object, and this object spawned a stack of several other object calls. The most obvious way would be to associate an event with a hierarchy of objects, but this seems messy and requires a lot of work.

Another solution that is under consideration is the use of an observer pattern by creating a publicly accessible static object that provides the event, and acts as a proxy between the lower-level object and the “upper-level” form.

Any recommendations?

Here is an example of pseudo code. In this example, MainForm instantiates "SomeObject" and attaches to the event. "SomeObject" is attached to the object that it creates, in order to transfer the event to the MainForm listener.

class Mainform { public void OnLoad() { SomeObject someObject = new SomeObject(); someObject.OnSomeEvent += MyHandler; someObject.DoStuff(); } public void MyHandler() { } } class SomeObject { public void DoStuff() { SomeOtherObject otherObject = new SomeOtherObject(); otherObject.OnSomeEvent += MyHandler; otherObject.DoStuff(); } public void MyHandler() { if( OnSomeEvent != null ) OnSomeEvent(); } public event Action OnSomeEvent; } 
+4
source share
5 answers

If your application is not based on Composite UI Application Blocks, the easiest solution is to place a listener class between the main form and other components that both classes can easily access. Conceptually, classes are laid out as follows:

  ---------- ----------------
     |  MainForm |  |  Some Component |
       --------- ----------------
           |  |
       Hooks onto Notifies
           |  |
            \ /
             -----------------
            |  Proxy Notifier |
             -----------------

Here is a sample code:

 using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { FakeMainForm form = new FakeMainForm(); form.CreateComponentAndListenForMessage(); Console.ReadKey(true); } } class FakeMainForm { public FakeMainForm() { Listener.AddListener(MessageRecieved); } void MessageRecieved(string msg) { Console.WriteLine("FakeMainForm.MessageRecieved: {0}", msg); } public void CreateComponentAndListenForMessage() { ComponentClass component = new ComponentClass(); component.PretendToProcessData(); } } class Listener { private static event Action<string> Notify; public static void AddListener(Action<string> handler) { Notify += handler; } public static void InvokeListener(string msg) { if (Notify != null) { Notify(msg); } } } class ComponentClass { public void PretendToProcessData() { Listener.InvokeListener("ComponentClass.PretendToProcessData() was called"); } } } 

This program displays the following:

  FakeMainForm.MessageRecieved: ComponentClass.PretendToProcessData () was called 

This code allows you to call methods directly on any listener, no matter how far apart they are in the call stack.

It is easy to rewrite it to your Listener class so that it is a little more general and works on different types, but you should get this idea.

+4
source

My initial intention was to try to avoid this, so that the area of ​​the object has obvious boundaries. In the specific case of Forms, I am trying to create a child parent form to manage all the necessary relationships with its ancestors. Can you clarify your case?

0
source

My first thought is that from the point of view of MainForm, she should not understand what is happening at 5 levels. He should know only about his interactions with the object that he spawned.

At the same time, if your main form wants to perform any action asynchronously, it should be able to do this by asynchronously calling the method on the generated object.

Now from the point of view of the created object, if you allowed your subscriber to execute any method asynchronously, there is no need to push the event model further ... just call the methods directly down the stack. You are already in another thread.

Hope this helps a bit. Just remember that the levels of your application should only know what is happening at the level below.

0
source

WPF uses routed events . They are static and can bubble or tunnel a tree of elements. I do not know if you are using WPF, but the idea of ​​static events can help you.

0
source

I would not say that this is a design error, there are good reasons why the main form wants to listen to what the object does. One of the scenarios I encountered displays status messages for the user to indicate which background processes are running, or what several controls are doing in a multi-threaded application that allows you to open multiple screens / "pages" at the same time.

In the Application Composite UI block, the basic equivalent of the dependency injection container provokes events when its copying objects in the same work item (the work item is only an object container for a group of related user controls). He does this by looking at special attributes, such as [EventPublication("StatusChanged")] for events and [EventSubscription("StatusChanged")] for public methods. One of my applications uses this functionality so that a user control embedded in internal applications can transmit status information (for example, “Downloading client data ... 45%”) without knowing that this data will be the status bar of the main form.

So UserControl can do something like this:

 public void DoSomethingInTheBackground() { using (StatusNotification sn = new StatusNotification(this.WorkItem)) { sn.Message("Loading customer data...", 33); // Block while loading the customer data.... sn.Message("Loading order history...", 66); // Block while loading the order history... sn.Message("Done!", 100); } } 

... where the StatusNotification class has an event with a signature of type

 [EventPublication("StatusChanged")] public event EventHandler<StatusEventArgs> StatusChanged; 

... and above, the Message() and Dispose() methods in this class raise this event accordingly. But this class clearly did not associate this event with anything. An object instance will automatically attach events to anyone who has a subscription attribute with the same name.

So, MainForm has an event handler that looks something like this:

 [EventSubscription("StatusChanged", ThreadOption=ThreadOption.UserInterface)] public void OnStatusChanged(object sender, StatusEventArgs e) { this.statusLabel.Text = e.Text; if (e.ProgressPercentage != -1) { this.progressBar.Visible = true; this.progressBar.Value = e.ProgressPercentage; } } 

... or some. This is more complicated since it will rotate through several status notifications in a certain number of seconds, since several user controls can broadcast status messages at the same time.

So, to recreate this behavior without switching to CAB (which, frankly, is much more complicated than I think it really should be), you can either have a MessageNotificationService object that you pass around your application or that you turn into a static / a single object (usually I avoid this approach, as it is harder to check), or you could create sub-user controls using the factory class, which will post events for you. Objects can be registered using the factory with attributes of your own creation or by explicitly calling methods that say "hey, when you create an object with the event of this signature, I want to know about it."

Just be careful that any class that you execute dispatches events when the object is deleted, because in this scenario it is stupid to end up with something that won't receive garbage collection.

Hope this helps!

0
source

All Articles