Reputation: 130
I have a form with a button, a label and a progress bar, so that when I click the button it creates an instance of class b to run a process. Once the process is done it will call an EventHandler to show "done" in the main form's label!
I created an event (SetStatusEvent) of a delegate (SetStatus) to do this. And it seems fine when I call this event outside the EventHandler (usbforProcessExited) but when I call it from usbforProcessExited it gives an error -
object reference not set to an instance of an object
main form
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;
}
}
class 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
}
}
Where is the problem?
Upvotes: 6
Views: 14613
Reputation: 2822
Jon Skeet has taught me that in c# 6.0 you can also use:
SetStatusEvent?.Invoke(100, "DONE", Color.Green);;
Upvotes: 3
Reputation: 62248
The event is null
, if noone subscribed to it yet.
So it's a good practice to control on null
equality, like:
public void usbforProcessExited(object sender, EventArgs f)
{
if(SetStatusEvent!=null)
SetStatusEvent(100, "DONE", Color.Green);
}
That's why outside it works well, as you have, this line:
rsSet.SetStatusEvent += new RemoteS.SetStatus(updateStatus);
so subscription and initilizaton of the event.
When you call it from inside, there is no any subscription made, so event is null
.
EDIT
Following the comments let's provide more thread safe approach of handling null reference check on event:
public void usbforProcessExited(object sender, EventArgs f)
{
var ev = SetStatusEvent; //[1]
if(ev!=null) //[2]
ev(100, "DONE", Color.Green);
}
Remember that assignment operation ia atomic in CLR, so even in between lines [1] and [2] someone else reset event to null, your ev
will be still valid and code will execute without crashing. IF this is desired behaviour or not it depends on your concrete case, so this is just another option to manage null reference control on event in thread-safe way.
Upvotes: 1
Reputation: 108790
You have a race condition:
usbforProcessExited
gets subscribed in the constructor of b
and might be invoked before you called rsSet.SetStatusEvent += new RemoteS.SetStatus(updateStatus)
.
You should only call usbfor.Start()
after you subscribed to SetStatusEvent
.
A related problem is that the event will run on another thread. You should set rsSet.SynchronizingObject
before starting the process so your event handler can modify the form without manually calling Invoke
/BeginInvoke
.
Upvotes: 4
Reputation: 440
The event is null, if there are no subscribers.
There are two solutions:
Initialize the event when declaring (dummy subscriber doing nothing):
public event SetStatus SetStatusEvent = delegate { };
Check the event for null before raising (in a thread-safe way):
public void usbforProcessExited(object sender, EventArgs f)
{
SetStatus setStatus = SetStatusEvent;
if (setStatus != null)
{
setStatus(100, "DONE", Color.Green);
}
}
Upvotes: 11