How to accept a function reference as an argument? - c#

I'm currently passing a EventHandler object to a util function like this:
Timer newTimer(int interval, System.Timers.ElapsedEventHandler handler) {
....
timer.Elapsed += handler;
....
}
newTimer(1000, new System.Timers.ElapsedEventHandler(myTimer_Tick));
But this is ugly and forces every caller to create an EventHandler object. How do I change that to something like this?
Timer newTimer(int interval, ref Function handler) {
....
timer.Elapsed += new System.Timers.ElapsedEventHandler(handler);
....
}
newTimer(1000, ref myTimer_Tick);

It's not clear why you're using ref here, but if myTimer_Tick has the right signature, you don't need to change your method at all - you can just use:
newTimer(1000, myTimer_Tick);
This uses a method group conversion instead an explicit delegate creation statement. It does the same thing though.
If you want to be able to use parameterless void methods, you could write a helper method to take an Action and wrap that in an ElapsedEventHandler via a lambda expression:
Timer StartTimer(int interval, Action action)
{
...
timer.Elapsed = (sender, args) => action();
...
}

Related

Subscribe to an Action event with a different return type method

I have an event:
public event Action OnDamageTaken;
I'd like to subscribe to it in my behavior tree, but with a return type of TaskStatus, which when evaluated to TaskStatus.Success, would end current Task execution in the tree, like so:
Vegetation.OnDestroy += VegetationDestroyed;
public TaskStatus VegetationDestroyed()
{
Owner.FindTask<JobChooser>().CurrentJob.OnJobCompleted();
return TaskStatus.Success;
}
However, I get the following error:
Is something like this possible to do, and if not, why? I thought since the Action event passes no arguments, therefore, we can subscribe with a method that has any return type, as long as that method too requires no parameters.
You can wrap the method inside a lambda:
Vegetation.OnDestroy += () => VegetationDestroyed();
If you want to unsubscribe again from the event, you have to store the lambda inside a variable:
Action onDestroy = () => VegetationDestroyed();
Vegetation.OnDestroy += onDestroy;
Vegetation.OnDestroy -= onDestroy;
Online demo: https://dotnetfiddle.net/AOYqbf

Error using TaskCompletionSource.TrySetResult()

This is a follow-up question to another SO question regarding the use of an async wrapper over an async callback function.
Here is the code as it stands (an excellent solution provided by #Servy):
static Task<ObservableCollection<MyResult>> GetMyDataAsync(Params p)
{
var tcs = new TaskCompletionSource<ObservableCollection<MyResult>>();
DoStuffClass stuff = new DoStuffClass();
stuff.LoadCompleted += (args) => tcs.TrySetResult(args.Result);
stuff.LongDrawOutProcessAsync(p);
return tcs.Task;
}
So, my problem is with the LoadCompleted event; here is the signature:
public event EventHandler<MyArgs> LoadCompleted;
MyArgs contains a property called ResultCollection; however, changing the code like this does not work:
stuff.LoadCompleted += (args) => tcs.TrySetResult(args.ResultCollection);
In fact, I get the error:
'System.EventHandler<MyArgs>' does not take 1 arguments
Which I can see if correct from the signature; so how can I set the LoadCompleted result to the TaskCompletionSource?
EventHandler needs 2 arguments, the first is the instance that raised the event and the second is the event arguments. You need to specify both of them even if you only use one (args).
This should work:
stuff.LoadCompleted += (sender, args) => tcs.TrySetResult(args.Result);
stuff.LoadCompleted += (sender, args) => tcs.TrySetResult(args.Result);
This should fix your problem
If you look at EventHandler<T> definition you will see it takes two arguments
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);
So you need to pass two arguments in your assignment
stuff.LoadCompleted += (sender, args) => tcs.TrySetResult(args.Result);

Method that takes a delegate as a parameter?

I'm trying to create a method that takes a delegate as one of its parameters but its not working. What am I doing wrong? I'm getting the error "callback is a variable but is used like a method" on the line that has windowAnimation.Completed += new EventHandler(callback).
private void animateWindowWidth(Window window, double width, double duration, Delegate callback)
{
window.BeginInit();
window.Dispatcher.BeginInvoke(new Action(() =>
{
DoubleAnimation windowAnimation = new DoubleAnimation();
windowAnimation.Duration = new Duration(TimeSpan.FromSeconds(duration));
windowAnimation.From = window.Width;
windowAnimation.To = width;
windowAnimation.FillBehavior = FillBehavior.HoldEnd;
windowAnimation.Completed += new EventHandler(callback);
window.BeginAnimation(Window.WidthProperty, windowAnimation);
}), null);
window.EndInit();
}
I just thought I'd explain a bit of why Delegate by itself does not work.
Delegate is not a true delegate, but a representation of one. It's basically a variable that holds a delegate. This is why an error is given for treating a variable like a method.
A simple example (granted you'd probably never do this) is if you have two delegates to do addition. One with ints and the other with floats. You can store the delegates in a Delegate object and pass that to another function that calls DynamicInvoke() on one variable:
void MyMethod(Delegate d)
{
d.DynamicInvoke(leftHandSide, rightHandSide);
}
No matter which of the two delegates are stored in the Delegate object, you get the appropriate functionality.
In your case, windowAnimation.Completed is expecting an actual delegate method, such as EventHandler. In addition, the constructor of EventHandler expects a delegate method. So using a Delegate object in either situation will not work.
You'll have to wrap it in a delegate or use a lambda function to place the true method call
windowAnimation.Completed += (s,e) => callback.DynamicInvoke();
or changed callback to an EventHandler and create a new one when you when you want to call this method.
You could change your method signature to:
private void animateWindowWidth(
Window window,
double width,
double duration,
EventHandler callback)
and that line that causes the error to
windowAnimation.Completed += callback;
Then create a new EvenHandler when you call the method.
Try to use Action instead of Delegate and replace this string:
windowAnimation.Completed += new EventHandler(callback);
with this:
windowAnimation.Completed += (s, e) => callback();
So, in the end it should look like this:
private void animateWindowWidth(Window window, double width, double duration, Action callback)
{
window.BeginInit();
window.Dispatcher.BeginInvoke(new Action(() =>
{
DoubleAnimation windowAnimation = new DoubleAnimation();
windowAnimation.Duration = new Duration(TimeSpan.FromSeconds(duration));
windowAnimation.From = window.Width;
windowAnimation.To = width;
windowAnimation.FillBehavior = FillBehavior.HoldEnd;
windowAnimation.Completed += (s, e) => callback());
window.BeginAnimation(Window.WidthProperty, windowAnimation);
}), null);
window.EndInit();
}

How to remove the event?

For Sample ....
SampleClass :
public class SampleClass
{
public delegate void BeforeEditorHandle();
public event BeforeEditorHandle OnBeforeEditor;
}
MainMethod
static void Main(string[] args)
{
SampleClass sc = new SampleClass();
// Add Event
sc.OnBeforeEditor +=new SampleClass.BeforeEditorHandle(sc_OnBeforeEditor);
// Remove Event
sc.OnBeforeEditor -= new SampleClass.BeforeEditorHandle(sc_OnBeforeEditor);
}
And , if I add the event by dynamic like this ...↓
sc.OnBeforeEditor += () => { };
Should I remove the event like ↓
sc.OnBeforeEditor -= () => { };
But I think this is very ugly when I have too much sources in the event....
Can anybody tell me the best way to remove the event please ?
You can assign the event handler/lambda to a variable which you can then subscribe and unsubscribe:
var myHandler = () => { };
sc.OnBeforeEditor += myHandler;
sc.OnBeforeEditor -= myHandler;
I'm pretty sure your code here won't work:
And , if I add the event by dynamic like this ...↓
sc.OnBeforeEditor += () => { };
Should I remove the event like ↓
sc.OnBeforeEditor -= () => { };
This is because restating the lambda creates a new different lambda.
You need to store the old reference and use it to unsubscribe:
BeforeEditorHandle myHandler=() => { }
sc.OnBeforeEditor += myHandler;
...
sc.OnBeforeEditor -= myHandler;
For easier unsubscribing you can collect your event handlers in a collection (For example List<BeforeEditorHandle>).
From MSDN:
It is important to notice that you
cannot easily unsubscribe from an
event if you used an anonymous
function to subscribe to it. To
unsubscribe in this scenario, it is
necessary to go back to the code where
you subscribe to the event, store the
anonymous method in a delegate
variable, and then add the delegate to
the event. In general, we recommend
that you do not use anonymous
functions to subscribe to events if
you will have to unsubscribe from the
event at some later point in your
code. For more information about
anonymous functions, see Anonymous
Functions (C# Programming Guide).

Removing anonymous event handler

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)

Categories