强制性事件未订阅

时间:2012-05-29 19:46:48

标签: c# .net winforms

问题:

我正在开发一个应用程序,在进行一些耗时的操作时,我应该在表单(WinForm)上显示一个带有取消按钮的进度条。显然我正在使用BackgroundWorker线程。下面的代码大致模拟了我想要实现的目标。

namespace WindowsFormsApplication1
{
    public delegate void SomeDelegateHandler();

    public partial class Form1 : Form
    {
        public event SomeDelegateHandler DoSomeAction;
        BackgroundWorker bgWorker;

        public Form1()
        {
            InitializeComponent();

            bgWorker = new BackgroundWorker();
            bgWorker.DoWork += new DoWorkEventHandler(bgWorker_DoWork);
        }

        void bgWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            //Some logic code here.
            for (int i = 0; i < 100; i++)
            {
                DoSomeAction();
            }
        }       

        private void Form1_Shown(object sender, EventArgs e)
        {
            if (DoSomeAction != null)
                bgWorker.RunWorkerAsync();
            else throw new EventNotSubscribedException();//Is this a valid style??
        }
    }

    public class EventNotSubscribedException : ApplicationException
    {
       //Some custom code here
    }
}

我的解决方案

根据上面的代码,只要表单显示给用户(OnShown事件),我就会启动后台工作线程。这是因为,用户无需为此发生任何动作。所以onshown做了耗时的操作工作。但问题是,正如我上面所示,主要耗时的工作是在其他类/组件上执行的,它也是一种紧密限制(遗留代码:无法重构)。因此,我已经在启动此表单的遗留代码类中订阅了事件DoSomeAction。

怀疑/问题:

如上所示抛出异常是否有效? (请在下面阅读我的理由)。

理由:

OnShown事件检查事件处理程序对象是否为null。这是因为,为了使这个表格可用,事件必须由订户订阅(使用代码),然后才能使用。如果没有,那么表单只是显示并且完全注意到并且使用代码可能不知道为什么它发生了。使用代码可以假设订阅该事件是选项,就像按每次说明的按钮点击事件一样。

希望我的帖子清晰易懂。

谢谢&amp;快乐的编码, 禅宗:))

2 个答案:

答案 0 :(得分:1)

你的意思是你需要向表单的调用者抛出异常吗?是使用showDialog还是Show调用?

顺便说一句,我不喜欢从事件中生成异常。相反,保持它以便从Form类上设置了一些状态的地方返回是相当不错的。

例如,我更喜欢使用

IsEventSubscribed = false this.Close()

而不是EventNotSubscribedException

BTW,我可以在代码中看到一个问题,当调用bgWorker_DoWork时,你应该将DoSomeAction检查为null,否则可能会导致NullReferenceException。

最好

  1. 从Form_shown
  2. 开始运行RunWorkerAsync
  3. 在DoWork中将Delegate检查为null,如果为null,则不要调用DoSomeAction,否则请调用它。
  4. 在BackgroundWorker的RunWorkerCompleted上,关闭表单。
  5. 如果您还需要更多信息,请与我们联系。

答案 1 :(得分:1)

我建议让使用代码构造BackgroundWorker并将其传递给表单的构造函数。您可以在构造函数中执行null测试,并在整个问题中执行一步。或者,将委托作为构造函数参数。我的意思是,消费代码在运营中期间需要更改工作代表的可能性有多大?


另一种方法是让对话框监视一个任务,而不是让对话框控制一个任务(就像你在这里一样)。例如,您可以使用如下界面:

public interface IMonitorableTask {
    void Start();

    event EventHandler<TData> TaskProgress;
}

其中TData是一种类型,提供更新对话框可能需要的任何信息(例如已完成的百分比)。

这样做的缺点是每个任务都需要是自己的一种。这可能导致非常丑陋,混乱的代码。您可以通过创建辅助类来缓解这个问题,例如:

public class DelegateTask : IMonitorableTask {
    private Action<Action<TData>> taskDelegate;

    public event EventHandler<TData> TaskProgress;

    public DelegateTask(Action<Action<TData>> taskDelegate) {
        if (taskDelegate == null)
            throw new ArgumentNullException("taskDelegate");

        this.taskDelegate = taskDelegate;
    }

    protected void FireTaskProgress(TData data) {
        var handler = TaskProgress;

        if (handler != null)
            handler(this, data);
    }

    public void Start() {
        taskDelegate(FireTaskProgress);
    }
}

然后你的任务方法成为工厂:

public IMonitorableTask CreateFooTask(object argument) {
    return new DelegateTask(progress => {
        DoStuffWith(argument);

        progress(new TData(0.5));

        DoMoreStuffWith(argument);

        progress(new TData(1));
    });
}

现在您可以轻松(*)支持命令行界面。只需将不同的监视器对象附加到任务的事件中即可。

(*)当然,取决于您的UI /逻辑分离的清洁程度。