我有一个带有按钮,标签和进度条的表单,因此当我单击该按钮时,它会创建一个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
}
}
问题出在哪里?
答案 0 :(得分:7)
如果没有订阅者,则该事件为空。
有两种解决方案:
声明(虚拟订阅者什么也不做)时初始化事件:
public event SetStatus SetStatusEvent = delegate { };
在提升之前检查事件是否为空(以线程安全的方式):
public void usbforProcessExited(object sender, EventArgs f)
{
SetStatus setStatus = SetStatusEvent;
if (setStatus != null)
{
setStatus(100, "DONE", Color.Green);
}
}
答案 1 :(得分:3)
你有竞争条件:
usbforProcessExited
在b
的构造函数中订阅,可能在调用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
仍然有效,代码将在不崩溃的情况下执行。如果这是所需的行为,它取决于你的具体情况,所以这只是以线程安全的方式管理事件的空引用控制的另一个选项。