In C#, how can a closure reference itself? The use case is to automatically unsubscribe from an event, after a single occurrence (or on some condition). I know you can create an instance of a delegate, but I'd really like to keep this as a closure, to maintain context. For example:
var someVar = new FooBar();
textBox1.TextChanged += (s, e) => {
doSomething(someVar);
// Unsubscribe from further events
// textBox1.TextChanged -= myself?
// If I don't unsubscribe, I'm needlessly keeping reference
// to someVar, and I don't intend to keep triggering this code
// upon further events.
};
As far as I know it cannot reference itself directly. However, you do something like this instead:
var someVar = new FooBar();
EventHandler closure = null;
closure = (s, e) => {
doSomething(someVar);
// Unsubscribe from further events
// textBox1.TextChanged -= myself?
// If I don't unsubscribe, I'm needlessly keeping reference
// to someVar, and I don't intend to keep triggering this code
// upon further events.
textBox1.TextChanged -= closure;
};
textBox1.TextChanged += closure;
Credit: Can an anonymous method in C# call itself?
Related
I have this code:
private void loadGENIOFileToolStripMenuItem_Click(object sender, EventArgs e)
{
OpenFileDialog dlgFile = new OpenFileDialog();
dlgFile.InitialDirectory = Properties.Settings.Default.PreviousPath;
dlgFile.Title = "Select GENIO file";
dlgFile.Filter = "GENIO files (*.txt)|*.txt";
dlgFile.FilterIndex = 0;
dlgFile.Multiselect = false;
if (dlgFile.ShowDialog() == DialogResult.OK)
{
Properties.Settings.Default.PreviousPath = Path.GetDirectoryName(dlgFile.FileName);
DeleteView();
m_oThreadServices.OnLoadingCompleted += (_sender, _e) =>
{
mruMenu.AddFile(dlgFile.FileName);
m_sUITInfo.dbDatabase = m_oThreadServices.GetDatabase();
CreateView();
};
m_oThreadServices.SetGenioFilePath(dlgFile.FileName);
m_oThreadServices.start();
}
}
But I am also trying to implement a MRU handler:
private void OnMruFile(int number, String filename)
{
if (File.Exists(filename))
{
Properties.Settings.Default.PreviousPath = Path.GetDirectoryName(filename);
DeleteView();
m_oThreadServices.OnLoadingCompleted += (_sender, _e) =>
{
mruMenu.SetFirstFile(number);
m_sUITInfo.dbDatabase = m_oThreadServices.GetDatabase();
CreateView();
};
m_oThreadServices.SetGenioFilePath(filename);
m_oThreadServices.start();
}
else
mruMenu.RemoveFile(number);
}
}
My m_oThreadServices.OnLoadingCompleted line of code seems to require that I use += and as a result, if I first load a file, it adds the first event handler. If I then go to use the MRU list to load a different file it ends up running two OnLoadingCompleted handlers.
I tried m_oThreadServices.OnLoadingCompleted = but it will not allow it. So what is the right way for me to intercept the event handler and not end up calling both sets of code? Am I going about it wrong?
Thank you.
You should make sure your event handlers are unsubscribed from the event source once the event is raised.
In order to do that, you have to modify a bit the anonymous handlers. For instance, this snippet:
m_oThreadServices.OnLoadingCompleted += (_sender, _e) =>
{
mruMenu.AddFile(dlgFile.FileName);
m_sUITInfo.dbDatabase = m_oThreadServices.GetDatabase();
CreateView();
};
should be like this:
EventHandler onLoadingCompleted = null;
onLoadingCompleted = (_sender, _e) =>
{
m_oThreadServices.OnLoadingCompleted -= onLoadingCompleted;
mruMenu.AddFile(dlgFile.FileName);
m_sUITInfo.dbDatabase = m_oThreadServices.GetDatabase();
CreateView();
};
m_oThreadServices.OnLoadingCompleted += onLoadingCompleted;
Same for the other.
The line
EventHandler onLoadingCompleted = null;
is needed to avoid using uninitialized variable compiler error here
m_oThreadServices.OnLoadingCompleted -= onLoadingCompleted;
You can remove a handler if it's a named function:
private void OnLoadingComplete_AddFile(_sender, _e)
{
mruMenu.AddFile(dlgFile.FileName);
m_sUITInfo.dbDatabase = m_oThreadServices.GetDatabase();
CreateView();
}
...
m_oThreadServices.OnLoadingCompleted += OnLoadingComplete_AddFile;
...
m_oThreadServices.OnLoadingCompleted -= OnLoadingComplete_AddFile;
Removing a handler that hasn't been added (or has already been removed) is a no-op, so you can just remove the "other" handler before you add one: this will ensure there is at most one handler.
So basically += is syntactic sugar for calling Combine on your event. Delegates are stored in an Invocation List, and the default behavior when an event is fired is for each delegate in the invocation list to get called in the order they were added. This is why you cannot simply set OnLoadingCompleted to one delegate with an = sign - an event stores a list of delegates, not one.
You could remove a delegate with -= (syntactic sugar for calling Remove). Perhaps you want to formally declare the previous delegate somewhere rather than passing it as a lambda. This would let you remove it when you are done with it.
There is no straightforward way of removing anonymous or unknown events from a handler. However, you can take a look at this forum posting on MSDN: https://social.msdn.microsoft.com/Forums/vstudio/en-US/45071852-3a61-4181-9a25-068a8698b8b6/how-do-i-determine-if-an-event-has-a-handler-already?forum=netfxbcl
There is some code and discussion about using reflection to remove delegates from your event handler.
It might be better though to understand exactly what you are wanting to accomplish. Perhaps there is a better way to get the end-result that you are looking for rather than rewire events.
It isn't usually good practice to remove established event code to change the behavior of the code you want to implement. It can lead to unintended consequences, and erratic behavior. If event code is defined, it is almost always best to keep it in place and design your application around it.
On the other hand, if this is code that is added by you, or in your code-base, you can remove it, if you have done the proper research to validate its removal and not cause the application to break elsewhere. The best way to do that would be to have the event code in a named function:
public void MyEventCode(object sender, EventArgs args)
{
// Do event stuff..
}
Then you can remove the event by name:
control.DoMyEvent -= MyEventCode;
I would like to call the event handler just one time, and then detach it. I tried to write:
EventHandler handler = (s, e) =>
{
// Do something
//
// blabla
// Detach the handler
SizeChanged -= handler;
};
SizeChanged += handler;
However on the line SizeChanged -= handler I get this error
Use of unassigned local variable 'handler'
Do you have idead on how I should proceed ? I thought about using a boolean flag, but I will do that only if I can't find a way to detach the handler.
The C# compiler will first create the lambda expression you wrote before assigning the result to the variable. So when the lambda is defined, handler doesn't have a value.
It works though if you assign a value of null to EventHandler before.
Since it's a closure and local variables are captured in the closure, at the time of the call handler will have the correct value and it will work:
EventHandler handler=null;
handler = (s, e) =>
{
// Do something
SizeChanged -= handler;
};
SizeChanged += handler;
To all people downvoting: It won't cause a NullReferenceException. handler is a local variable which is captured in the closure, so the value of handler inside the lambda will change, when it changes in the method that contains the closure. I tested it actually on my PC and it works perfectly.
This is because it really is unassigned yet. Try making a named method out of it, so the symbol is known prehand.
private void OnEvent(object sender, EventArgs e)
{
// Do something
AnEvent -= OnEvent;
}
private void RegisterOnce()
{
AnEvent += OnEvent;
}
I would also recommend to run the DoSmething code only after detatch and implement some locking mechanism, in case you have multithrading, to prevent from multiple threads call the event at the exact same time, not having time to detatch and therefore, all run.
Consider the situation in which you want to subscribe to an event for one and only one notification. Once the first notification lands, you unsubscribe from all future events. Would the following pattern present any memory issues? It works, but I wasn't sure if the self-referencing closure could keeps things around in memory longer than desired.
public class Entity
{
public event EventHandler NotifyEvent;
}
// And then, elsewhere, for a listen-once handler, we might do this:
Entity entity = new Entity();
Action<object, EventArgs> listener = null;
listener = (sender, args) =>
{
// do something interesting
// unsubscribe, so we only get 1 event notification
entity.NotifyEvent -= new EventHandler(listener);
};
entity.NotifyEvent += new EventHandler(listener);
Note that you have to declare 'listener' and assign a value (null). Otherwise the compiler complains about 'Use of unassigned local variable listener'
There is nothing wrong with this pattern. It's the very same pattern I and many others use for assigning and removing a lambda expression to an event handler.
While I think the general pattern is fine, I wouldn't go through Action<object, EventArgs>. I'd use:
EventHandler listener = null;
listener = (sender, args) =>
{
// do something interesting
// unsubscribe, so we only get 1 event notification
entity.NotifyEvent -= listener;
};
entity.NotifyEvent += listener;
I have the following code where SprintServiceClient is a reference to a WCF Service-
public class OnlineService
{
private SprintServiceClient _client;
public OnlineService()
{
_client = new SprintServiceClient();
}
public void AddMemberToTeam(MemberModel user, int projectId, Action<int> callback)
{
_client.AddMemberToTeamCompleted += (s, e) => callback(e.Result);
_client.AddMemberToTeamAsync(user.ToUser(), projectId);
}
}
the problem is that every time AddMemberToTeam is called it adds another callback to client.AddMemberToTeamCompleted
i.e the first time AddMemberToTeam is called the callback is called once, the second time AddMemberToTeam is called the callback is called twice ect.
Is there any way to remove the eventhandler from AddMemberToTeamCompleted once the eventhandler has been called or use another method which takes in the callback?
You can refer to your anonymous method from inside itself as long as you assign a delegate to a variable first:
EventHandler<SomeEventArgs> handler = null;
handler = (s, e) =>
{
_client.AddMemberToTeamCompleted -= handler;
callback(e.Result);
};
_client.AddMemberToTeamCompleted += handler;
Note that you need to declare the variable and assign it separately or the compiler will deem it uninitialized when you come to use it inside the method body.
The trick to making a self-unsubscribing event-handler is to capture the handler itself so you can use it in a -=. There is a problem of declaration and definite assignment, though; so we can't do something like:
EventHandler handler = (s, e) => {
callback(e.Result);
_client.AddMemberToTeamCompleted -= handler; // <===== not yet defined
};
So instead we initialize to null first, so the declaration is before the usage, and it has a known value (null) before first used:
EventHandler handler = null;
handler = (s, e) => {
callback(e.Result);
_client.AddMemberToTeamCompleted -= handler;
};
_client.AddMemberToTeamCompleted += handler;
No there is no way,
Apparantly Tim and Marc have another nice solution
But you can always just name them, and do the -= on the named eventhandler on this method ;)
Guessing your event:
_client.AddMemberToTeamCompleted += OnAddMemberToTeamCompleted;
and
public void OnAddMemberToTeamCompleted(object sender, EventArgs args)
{
_client.AddMemberToTeamCompleted -= OnAddMemberToTeamCompleted;
callback(e.Result)
}
Next problem is getting this callback in your listener. Perhaps putting it on a Property in the EventArgs (but that feels kinda dirty, I agree)
I'm using global variable named "client"
For example
client.getPagesCompleted += (s, ee) =>
{
pages = ee.Result;
BuildPages(tvPages.Items, 0);
wait.Close();
};
client.getPagesAsync(cat.MainCategoryID);
I need to clear handlers for getPagesCompleted and set another handler. How to easy clear handles?
I know client.getPagesCompleted-=new EventHandler(...). But it is very difficult. I need easy way.
I'm using client.getPagesCompleted=null but error shown. "only use += / -+"
The only way to remove an event handler is to use the -= construct with the same handler as you added via +=.
If you need to add and remove the handler then you need to code it in a named method rather using an anonymous method/delegate.
You don't have to put your event handler in a separate method; you can still use your lambda function, but you need to assign it to a delegate variable. Something like:
MyEventHandler handler = (s, ee) =>
{
pages = ee.Result;
BuildPages(tvPages.Items, 0);
wait.Close();
};
client.getPagesCompleted += handler; // Add event handler
// ...
client.getPagesCompleted -= handler; // Remove event handler
Save the event object to a variable, and use -= to unsubscribe.