C# 为什么事件是空的?(对象引用未设置为对象的实例)
我有一个带有按钮、标签和进度条的表单,因此当我单击按钮时,它会创建一个类b的实例来运行进程。流程完成后,它将调用EventHandler在主窗体的标签中显示“完成” 为此,我创建了一个委托(SetStatus)的事件(SetStatusEvent)。当我在EventHandler(usbforProcessExited)之外调用这个事件时,它看起来很好,但是当我从usbforProcessExited调用它时,它给出了一个错误-C# 为什么事件是空的?(对象引用未设置为对象的实例),c#,events,delegates,null,invoke,C#,Events,Delegates,Null,Invoke,我有一个带有按钮、标签和进度条的表单,因此当我单击按钮时,它会创建一个类b的实例来运行进程。流程完成后,它将调用EventHandler在主窗体的标签中显示“完成” 为此,我创建了一个委托(SetStatus)的事件(SetStatusEvent)。当我在EventHandler(usbforProcessExited)之外调用这个事件时,它看起来很好,但是当我从usbforProcessExited调用它时,它给出了一个错误- object reference not set to an in
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
}
}
问题出在哪里?如果还没有人订阅,则事件为
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
仍将有效,代码将在不崩溃的情况下执行。如果这是期望的行为,则取决于您的具体情况,所以这只是以线程安全的方式管理事件上的空引用控件的另一个选项 您有一个竞赛条件:
usbforProcessExited
在b
的构造函数中订阅,并且可能在调用rsSet.SetStatusEvent+=new RemoteS.SetStatus(updateStatus)
之前被调用
只有在订阅了SetStatusEvent
之后,才可以调用usbfor.Start()
一个相关的问题是事件将在另一个线程上运行。您应该在启动流程之前设置
rsSet.SynchronizingObject
,以便事件处理程序可以修改表单,而无需手动调用Invoke
/BeginInvoke
如果没有订阅服务器,则事件为空
有两种解决方案:
Jon Skeet告诉我,在c#6.0中,您还可以使用:
SetStatusEvent?.Invoke(100, "DONE", Color.Green);;
除非这不是线程安全的-如果最后一个订户在检查和呼叫之间取消订阅,您将以NullReferenceException结束。这可能是问题,也可能不是问题,这取决于您希望支持的线程代码>首先(尽管我找不到原因的参考)空检查(即使正确完成)会导致OPs代码中的丢失事件。所以这不是这里的解决方案。@CodesInChaos:他说的是“内部”和“外部”,我相信他的意思是分别从“b”类和“main”形式调用该函数。不要认为这里涉及任何提升条件,但是对事件初始化的简单错误理解-@CodesInChaos即使它可能无法解决当前问题,也应该正确编码。你应该学会生成最少的工作示例。您的代码包含许多与此问题无关的内容。我认为这不是问题的原因,但您是否需要“usbfor.EnableRaisingEvents=true”才能引发已退出事件?@ChrisSpicer OPS!事实上我有那条线!我只是想尽量减少我发布的代码量。谢谢你提到这一部分。你可以考虑用<代码>任务替换你的<代码> b>代码>类及其事件。空校验(即使正确完成)会导致OPS代码中的丢失事件。所以这不是解决方案。在这种情况下,空检查只会隐藏症状。@daygoor在编写Raise事件方法时,您仍然应该使用此模式。正如你用自己的话所说(这很有趣,因为你是OP),“如果做得正确”。
public void usbforProcessExited(object sender, EventArgs f)
{
SetStatus setStatus = SetStatusEvent;
if (setStatus != null)
{
setStatus(100, "DONE", Color.Green);
}
}
SetStatusEvent?.Invoke(100, "DONE", Color.Green);;