在C#中附加到远在调用堆栈中的事件的最佳方法?

时间:2008-12-05 22:02:56

标签: c# events

“顶级”课程的最佳设计决策是将一个事件附加到一个可能是“在callstack中5层以下”的类中?

例如,也许MainForm已经生成了一个对象,该对象产生了一个其他几个对象调用的callstack。最明显的方法是将事件链接到对象层次结构,但这看起来很混乱,需要大量工作。

我看到的另一个解决方案是通过创建一个公开访问的静态对象来使用观察者模式,该对象公开事件,并充当底层对象和顶级“表单”之间的代理。

有什么建议吗?

这是一个伪代码示例。在此示例中,MainForm实例化“SomeObject”,并附加到事件。 'SomeObject'附加到它实例化的对象,以便将事件传递给MainForm侦听器。

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;
}

5 个答案:

答案 0 :(得分:4)

如果您的应用程序不是基于复合UI应用程序块,最简单的解决方案是在主窗体和其他组件之间放置一个“监听器”类,这两个类都可以轻松访问。从概念上讲,这些课程的布局如下:

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

以下是一些示例代码:

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");
        }
    }
}

该程序输出以下内容:

FakeMainForm.MessageRecieved: ComponentClass.PretendToProcessData() was called

此代码允许您直接在任何侦听器上调用方法,无论它们在调用堆栈中有多远。

很容易重写你的Listener类,使它更通用,适用于不同的类型,但你应该明白这一点。

答案 1 :(得分:0)

我的初衷是尝试避免这种情况,以便对象的范围具有明显的界限。在Forms的特定情况下,我会尝试让孩子的父母表格与其祖先一起管理所有必需的通信。你能更具体地谈谈你的案子吗?

答案 2 :(得分:0)

我的第一个想法是,从你的MainForm的角度来看,它应该不知道5级下来会发生什么。它应该只知道它与它产生的对象的相互作用。

有了这个,如果你的主窗体想要异步执行一些动作,它应该能够通过异步调用生成的对象上的方法来做到这一点。

现在从您生成的对象的角度来看,如果您允许调用者异步执行某些方法,则无需进一步向下推动事件模型...只需将方法直接调用到堆栈中即可。你已经在另一个主题上了。

希望这有点帮助。只需记住你的应用程序的级别应该只知道它们下面的级别发生了什么。

答案 3 :(得分:0)

WPF使用routed events。这些是静态的,可以在元素树中冒泡或隧道向下。我不知道您是否使用WPF,但静态事件的想法可能会帮助您。

答案 4 :(得分:0)

我不会说这是一个设计错误,主要表单有理由想要听一个对象在做什么。我遇到的一个场景是向用户显示状态消息,以指示后台进程正在执行的操作,或多个控件在多线程应用程序中执行的操作,这些应用程序允许您同时打开多个屏幕/“页面”。

在复合UI应用程序块中,依赖项注入容器的基本等效项在其在同一工作项中实例化对象时将事件连接起来(工作项只是一组相关用户控件的对象容器)。它通过扫描事件上的[EventPublication("StatusChanged")]和公共方法上的[EventSubscription("StatusChanged")]等特殊属性来实现此目的。我的一个应用程序使用此功能,以便在应用程序内部实例化的用户控件可以广播状态信息(例如“加载客户数据... 45%”),而不知道该数据最终会在主窗体的状态栏。

所以UserControl可以这样做:


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);
    }
}

... StatusNotification类的event类似

的签名

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

...并且该类上面的Message()Dispose()方法适当地调用该事件。但那个班并没有明确地把这个事件搞砸了。对象实例化器将自动将事件连接到具有相同名称的订阅属性的任何人。

因此,MainForm有一个事件处理程序,如下所示:


[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;
   }
}

......或者其他一些。它比这更复杂,因为它会在给定的秒数内轮换多个状态通知,因为多个用户控件可以在同一时间广播状态消息。

所以要重新创建这种行为而不实际转换到CAB(说实话,它比我认为真的需要复杂得多),你可以有一个MessageNotificationService对象,你传递你的应用程序或你变成一个静态/单一对象(我通常避免这种方法,因为它更难测试),或者你可以让你的子用户控件由工厂类实例化,为你做事件连线。对象可以通过您自己创建的属性向工厂注册,也可以通过显式调用“嘿,任何时候使用此签名的事件创建对象,我想知道它。”

请小心让你实现的任何类在对象被释放时解开事件,因为在这种情况下它很容易变得容易被垃圾收集。

希望这有帮助!