为什么一个事件是空的? (你调用的对象是空的)

时间:2013-07-31 07:30:20

标签: c# events delegates null invoke

我有一个带有按钮,标签和进度条的表单,因此当我单击该按钮时,它会创建一个b类实例来运行一个进程。一旦完成该过程,它将调用EventHandler在主窗体的标签中显示“done”!

我创建了一个委托事件(SetStatusEvent)来执行此操作。当我在EventHandler(usbforProcessExited)之外调用此事件时似乎很好但是当我从usbforProcessExited调用它时它会出错 -

object reference not set to an instance of an object

主要表单

public partial class main : Form
{
    b rsSet = new b();

    public main()
    {
        InitializeComponent();
        rsSet.SetStatusEvent += new RemoteS.SetStatus(updateStatus);
    }

    private void button1_Click(object sender, EventArgs e)
    {
        rsSet.FormatUSB();
    }

    private delegate void UpdateStatus(int i, string str, Color clr);

    private void SetStatus(int i, string str, Color clr)
    {
        this.progressBar1.Value = i;
        this.lbl_status.ForeColor = clr;
        this.lbl_status.Text = str;
    }

    private void updateStatus(int i, String msg, Color color)
    {
        object[] p = GetInokerPara(i, msg, color);
        BeginInvoke(new UpdateStatus(SetStatus), p);
    }

    private object[] GetInokerPara(int progress, string msg, Color color)
    {
        object[] para = new object[3];
        para[0] = progress;
        para[1] = msg;
        para[2] = color;

        return para;
    }
}

b级

class b
{
    public delegate void SetStatus(int i, string msg, Color color);
    public event SetStatus SetStatusEvent;

    System.Diagnostics.Process usbfor = new System.Diagnostics.Process();

    public void FormatUSB()
    {

        usbfor.StartInfo.FileName = @"usbformat.bat";
        usbfor.EnableRaisingEvents = true;
        usbfor.Exited += new EventHandler(usbforProcessExited);
        usbfor.Start();
    }

    public void usbforProcessExited(object sender, EventArgs f)
    {
        SetStatusEvent(100, "DONE", Color.Green); //ERROR HERE! (object reference not set to an instance of an object
    }
}

问题出在哪里?

4 个答案:

答案 0 :(得分:7)

如果没有订阅者,则该事件为空。

有两种解决方案:

  1. 声明(虚拟订阅者什么也不做)时初始化事件:

    public event SetStatus SetStatusEvent = delegate { };
    
  2. 在提升之前检查事件是否为空(以线程安全的方式):

    public void usbforProcessExited(object sender, EventArgs f)
    {
        SetStatus setStatus = SetStatusEvent;
        if (setStatus != null)
        {
            setStatus(100, "DONE", Color.Green);
        }
    }
    

答案 1 :(得分:3)

你有竞争条件:

usbforProcessExitedb的构造函数中订阅,可能在调用rsSet.SetStatusEvent += new RemoteS.SetStatus(updateStatus)之前调用。

您只应在订阅usbfor.Start()后致电SetStatusEvent

相关问题是该事件将在另一个线程上运行。您应该在开始此过程之前设置rsSet.SynchronizingObject,以便您的事件处理程序可以修改表单,而无需手动调用Invoke / BeginInvoke

答案 2 :(得分:2)

Jon Skeet告诉我,在c#6.0中你也可以使用:

SetStatusEvent?.Invoke(100, "DONE", Color.Green);;

答案 3 :(得分:1)

如果还没有订阅,则事件为null。 因此,控制null平等是一种很好的做法,例如:

    public void usbforProcessExited(object sender, EventArgs f)
    {
        if(SetStatusEvent!=null)
            SetStatusEvent(100, "DONE", Color.Green);
    }

这就是为什么以外它运作良好,正如你所知,这一行:

 rsSet.SetStatusEvent += new RemoteS.SetStatus(updateStatus);

所以订阅和启动活动。

当你从里面中调用它时,没有任何订阅,所以事件是null

编辑

在评论之后,让我们提供更多线程安全的方法来处理事件的空引用检查:

   public void usbforProcessExited(object sender, EventArgs f)
    {
        var ev = SetStatusEvent; //[1]
        if(ev!=null) //[2]
            ev(100, "DONE", Color.Green);
    }

请记住,CLR中的赋值操作是原子的,所以即使在行[1]和[2]之间,其他人将事件重置为null,您的ev仍然有效,代码将在不崩溃的情况下执行。如果这是所需的行为,它取决于你的具体情况,所以这只是以线程安全的方式管理事件的空引用控制的另一个选项。