Creating a method to subscribe to an event but fire only once - c#

I'm trying to write a method that attaches adds itself to an event, and then removes itself after it was invoked.
Those are my failed attempts:
https://imgur.com/a/PvrPUYY
public Action myEvent = () => {};
void Bar(){
Action invokeThenUnsubscribe = null;
invokeThenUnsubscribe = () =>
{
Debug.Log("Attempt 0");
myEvent -= invokeThenUnsubscribe;
};
myEvent += invokeThenUnsubscribe;
SubscribeOnce(myEvent, () => Debug.Log("Attempt 1"));
myEvent.SubscribeOnce(() => Debug.Log("Attempt 2"));
myEvent.Invoke();
myEvent.Invoke();
}
public Action SubscribeOnce( Action a, Action actionToSubscribe ) {
Action invokeThenUnsubscribe = null;
invokeThenUnsubscribe = () =>
{
actionToSubscribe.Invoke();
a -= invokeThenUnsubscribe;
};
a += invokeThenUnsubscribe;
return a;
}
public static class ActionsEx {
public static void SubscribeOnce(this Action a, Action actionToSubscribe){
Action invokeThenUnsubscribe = null;
invokeThenUnsubscribe = () => {
actionToSubscribe.Invoke();
a -= invokeThenUnsubscribe;
};
a += invokeThenUnsubscribe;
}
}
I realize it's happening because, after using +=, I lose the reference to the new event.
Does anyone have any idea how I can achieve the effect I'm looking for?
Thanks in advance

When you call SubscribeOnce, you aren't passing a reference to "the event" (technically a field, but: semantics); instead, you're reading the current value of the event (field), and passing that to the method. At that point, it the parameter value is completely divorced from the event (field), and no change to a (now a captured parameter) will have any effect on the original event (field). You're only "unsubscribing" a local delegate that has nothing to do with the event (field).
To do what you'd want, you'd need to pass the originating object in as the parameter, i.e. something like:
public static void SubscribeOnce(this SomeType obj, Action actionToSubscribe){
Action invokeThenUnsubscribe = null;
invokeThenUnsubscribe = () => {
actionToSubscribe.Invoke();
obj.TheEvent -= invokeThenUnsubscribe;
};
obj.TheEvent += invokeThenUnsubscribe;
}
}
where SomeType (which could be an interface) defines an event Action TheEvent;, with usage this.SubscribeOnce(() => Debug.Log("Attempt 2")); (or whatever object you want to use)

I am not crazy about the fact that you are mixing actions with events. Instead, I would use a more object oriented approach and separate the two concepts by creating an interface to represent the subscribable objects that expose some event and then use an extension method to allow all actions to subscribe once to these objects:
//Delegate to handle events
public delegate void SomeEventEventHandler(ISubscribable sender, EventArgs args);
//Interface that describes an object you can subscribe to
public interface ISubscribable
{
event SomeEventEventHandler MyEvent;
}
//Implementation of a subscribable object (base class)
public class SomeObject : ISubscribable
{
private SomeEventEventHandler _handler;
public event SomeEventEventHandler MyEvent
{
add { _handler += value; }
remove { _handler -= value; }
}
public void RaiseEvent()
{
_handler?.Invoke(this, new EventArgs());
}
}
//Extension method for actions
public static class ActionExtensions
{
public static void SubscribeOnce(this Action action, ISubscribable subscribable)
{
SomeEventEventHandler handler = null;
handler = new SomeEventEventHandler((ISubscribable subscribable, EventArgs args) =>
{
action();
subscribable.MyEvent -= handler;
});
subscribable.MyEvent += handler;
}
}
//Usage:
var obj = new SomeObject();
obj.MyEvent += (ISubscribable sender, EventArgs args) =>
{
Console.WriteLine("Attempt 0 (always fires).");
};
Action invokeThenUnsubscribe = () => { Console.WriteLine("Attempt 1"); };
invokeThenUnsubscribe.SubscribeOnce(obj);
Action invokeThenUnsubscribe2 = () => { Console.WriteLine("Attempt 2"); };
invokeThenUnsubscribe2.SubscribeOnce(obj);
obj.RaiseEvent();
obj.RaiseEvent();
obj.RaiseEvent();
Output:
Attempt 0 (always fires).
Attempt 1
Attempt 2
Attempt 0 (always fires).
Attempt 0 (always fires).
Alternatively, you can skip the action extension all together and provide this functionality as a method of the class:
//In class SomeObject
public void SubscribeOnce(Action action)
{
SomeEventEventHandler handler = null;
handler = new SomeEventEventHandler((ISubscribable subscribable, EventArgs args) =>
{
action();
subscribable.MyEvent -= handler;
});
this.MyEvent += handler;
}
//Usage:
var obj2 = new SomeObject();
obj2.MyEvent += (ISubscribable sender, EventArgs args) =>
{
Console.WriteLine("Attempt 0 (always fires).");
};
obj2.SubscribeOnce(() => { Console.WriteLine("Attempt 1"); });
obj2.SubscribeOnce(() => { Console.WriteLine("Attempt 2"); });
obj2.RaiseEvent();
obj2.RaiseEvent();
obj2.RaiseEvent();
This produces the same output.

Related

How to remove one of multicast delegate by passing Action<T> Method or Target

Zero to many methods are added from on or more methods on classes
I pass a Guid as arg
Currently, I have subscribed the EventHandler as a wrapped action
When unsubscribing the Action I expect to still be able to fire the other delegates.
public class SomeHandler : IDisposable
{
private event EventHandler<Guid>? _onChangedSync;
public void AddOnChanged(Action<Guid> eventDelegate)
=> _onChangedSync += (_, arg) => eventDelegate(arg);
public void UnsubscribeOnChanged(Action<Guid> actionEvent)
{
var invocations = _onChangedSync?.GetInvocationList().Cast<EventHandler<Guid>>().ToList();
var found = invocations?.FirstOrDefault(del => del.Method == actionEvent.Method);
_onChangedSync -= found;
}
public void Change(Guid clinicId)
{
if (_onChangedSync != null)
{
_onChangedSync.Invoke(this, clinicId);
}
}
public void Dispose()
{
if (_onChangedSync != null)
{
foreach (var e in _onChangedSync.GetInvocationList().Cast<EventHandler<Guid>>())
{
_onChangedSync -= e;
}
}
GC.SuppressFinalize(this);
}
}
This is the unit test I use.
Subscribe 2 separate methods/actions
Unsubscribe 1 of the methods/actions
Expect only one of them to be called
Currently _callCount == 2 after run test
public class SomeTests
{
private int _callCount;
private void ListenAction1(Guid arg)
=> _callCount++;
private void ListenAction2(Guid arg)
=> _callCount++;
[Fact]
public void Unsubscribed_events_must_not_be_invoked()
{
// Given
var testArg = Guid.Parse("54b9bfde-ef2b-4aab-b705-4c59371c0e4f");
using var state = new SomeHandler();
state.AddOnChanged(ListenAction1);
state.AddOnChanged(ListenAction2);
state.UnsubscribeOnChanged(ListenAction1);
// When
state.Change(testArg);
// Then
Assert.Equal(expected: 1, _callCount);
}
}
How do I remove one of the subscribed from the EventHandler by passing the action again and comparing it with the rest of the invocations?

How to declare events inside a dictionary initialization?

I am creating a Dictionary of events and I want to declare those events inside the initialization of that dictionary instead of declaring them somewhere else and placing the links to the dictionary.
static event EventDelegate Event1;
static event EventDelegate Event2;
static event EventDelegate Event3;
public enum EventTypes
{
Event1,
Event2,
Event3,
}
public static Dictionary<EventTypes, EventDelegate> events = new Dictionary<EventTypes, EventDelegate>
{
{EventTypes.Event1, Event1},
{EventTypes.Event2, Event2},
{EventTypes.Event3, Event3},
};
So I want to do something like that:
{EventTypes.Event1, new event EventDelegate Event1}
Is that possible?
How about wrapping the events?
class MyEventWrapper
{
public event EventDelegate Handlers;
public void Raise(object sender, EventArgs args)
{
Handlers?.Invoke(sender, args);
}
}
//
Dictionary<EventTypes, MyEventWrapper> eventMap = new Dictionary<EventTypes, MyEventWrapper>
{
{ EventTypes.Event1, new MyEventWrapper() },
{ EventTypes.Event2, new MyEventWrapper() },
};
//
eventMap[EventTypes.Event1].Handlers += (s, a) => { };
eventMap[EventTypes.Event2].Handlers += (s, a) => { };
//
eventMap[EventTypes.Event1].Raise(this, new EventArgs());
If you define your dictionary like this:
public delegate void EventDelegate(object data);
public static Dictionary<EventTypes, EventDelegate> Events =
new Dictionary<EventTypes, EventDelegate>
{
{ EventTypes.Event1, (EventDelegate)((_) => { }) },
{ EventTypes.Event2, (EventDelegate)((_) => { }) },
{ EventTypes.Event3, (EventDelegate)((_) => { }) },
};
public enum EventTypes
{
Event1,
Event2,
Event3,
}
Then this code works a treat:
Events[EventTypes.Event1] += (object data) => Console.WriteLine($"Event1 (1): {data}");
Events[EventTypes.Event1] += (object data) => Console.WriteLine($"Event1 (2): {data}");
Events[EventTypes.Event2] += (object data) => Console.WriteLine($"Event2: {data}");
Events[EventTypes.Event1]("A");
Events[EventTypes.Event2]("B");
Events[EventTypes.Event3]("C");
The output I get is:
Event1 (1): A
Event1 (2): A
Event2: B
You are then clearly declaring those events inside the initialization of the dictionary.

Eventhandler not getting unsubscribed

In my silverlight application, I am passing a method GetListCallBack to a delegate parameter of another method GetEmployees in Repository class which attaches that delegate as eventhandler to completed event of an async service call.
EmpViewModel Class:
public class EmpViewModel
{
private IRepository EMPRepository = null;
//constructor
public EmpViewModel
{
this.EMPRepository= new Repository();
}
public void GetList()
{
this.EMPRepository.GetEmployees(xyz, this.GetListCallBack);
}
public void GetAnotherList()
{
this.EMPRepository.GetEmployees(pqr, this.GetAnotherListCallBack);
}
private void GetListCallBack(object sender, GetListCompletedEventArgs args)
{
if (args.Error == null)
{
this.collection1.Clear();
this.collection1 = args.Result;
}
else
{
//do sth
}
}
public void GetAnotherListCallback(object sender, GetListCompletedEventArgs args)
{
//do sth with collection1
}
}
Repository Class:
public class Repository : IRepository
{
private readonly ServiceClient _client=null ;
public Repository()
{
_client = new ServiceClient(Binding, Endpoint);
}
public void GetEmployees(int xyz, EventHandler<GetListCompletedEventArgs> eventHandler)
{
_client.GetListCompleted -= eventHandler;
_client.GetListCompleted += new EventHandler<GetListCompletedEventArgs>(eventHandler);
_client.GetListAsync(xyz);
}
}
Now, when a call to the method GetList() has been completed and then if I call another method GetAnotherList() in same class EmpViewModel, then GetListCallBack method gets called again before GetAnotherListCallBack gets called.
This is probably happening as both methods get subscribed to the event.
As you can see, I have explicitly unsubscribed the eventhandler from the callback event but still the eventhandler is getting invoked.
Can anyone please suggest where I may be going wrong?
EDIT:
When I use a local variable instead of using this.EMPRepository to call the Repository method it works well as both CallBack methods are passed to different instances of Repository class and only the attched CallBack method gets fired
public class EmpViewModel
{
public void GetList()
{
EMPRepository = new Repository();
EMPRepository.GetEmployees(xyz, this.GetListCallBack);
}
public void GetAnotherList()
{
EMPRepository = new Repository();
EMPRepository.GetEmployees(pqr, this.GetAnotherListCallBack);
}
--------
First:
_client.GetListCompleted += new EventHandler<GetListCompletedEventArgs>(eventHandler);
is (seen from a functional point of view) the same as:
_client.GetListCompleted += eventHandler;
And now you immediate see the problem. Your code is:
_client.GetListCompleted -= eventHandler;
_client.GetListCompleted += eventHandler;
Why remove an eventhandler if you add it in the next line.
I guess you want to remove the old eventhandler and add a new one. So your function should get a delegate to the old eventhandler to remove. Something like this:
public void GetEmployees(int xyz, EventHandler<GetListCompletedEventArgs> oldEventHandler, EventHandler<GetListCompletedEventArgs> newEventHandler)
{
_client.GetListCompleted -= oldEventHandler;
_client.GetListCompleted += newEventHandler;
_client.GetListAsync(xyz);
}
But is that even possible?
If you have control over ServiceClient.GetListCompleted, why not remove the event keyword and just assign that delegate, like:
public void GetEmployees(int xyz, EventHandler<GetListCompletedEventArgs> eventHandler)
{
_client.GetListCompleted = eventHandler;
_client.GetListAsync(xyz);
}
Or... if the delegate is only called once per GetListASync:
public void GetEmployees(int xyz, EventHandler<GetListCompletedEventArgs> eventHandler)
{
_client.GetListCompleted += (sender, args) =>
{
eventHandler(sender, e);
_client.GetListCompleted -= eventHandler;
};
_client.GetListAsync(xyz);
}

Adding an action to event handler arguments c#

I have a problem with some code I need to refactor. Right now it uses lambdas as event handlers, but they are not removed properly. From what I have read, this is not even possible? Anyway I would like to rewrite it to use a delegate instead of an anonymous function, and now my problem is that right now it takes an action as parameter, and I can't seem to figure out how to pass the action on to my new delegate. This is the code:
void RetrieveData(
int pointId,
int? chartCollectionId,
Action action)
{
if (pointId <= 0)
throw new ArgumentException("PointId not valid");
LastPointId = NextPointId;
NextPointId = pointId;
Clear();
_csr = new CustomerServiceRepository();
_csr.ServiceClient.GetChartDataCompleted += (se, ea) =>
{
_cachedCharts = ea.Result;
ChartDataRetrieved(ea.Result);
if (action != null)
action.Invoke();
_csr = null;
};
_csr.ServiceClient.GetChartDataAsync(
Settings.Current.Customer.CustomerName,
pointId,
chartCollectionId);
_csr.ServiceClient.GetChartDataCompleted -= (se, ea) => //remove after usage
{
_cachedCharts = ea.Result;
ChartDataRetrieved(ea.Result);
if (action != null)
action.Invoke();
_csr = null;
};
}
I was thinking that maybe I could create the following:
public class extendedEventArgs : GetChartDataCompletedEventArgs
{
Action foo { get; set; }
}
void tang(object sender, extendedEventArgs e)
{
_cachedCharts = e.Result;
ChartDataRetrieved(e.Result);
if (action != null)
action.Invoke();
_csr = null;
}
And the pass the action as a parameter in the extended event args, but when I try to use it like this
_csr.ServiceClient.GetChartDataCompleted += new EventHandler<extendedEventHandler>(tang);
It gives an error:
Cannot implicitly convert type System.EventHandler<Conwx.Net.Client.CustomerClient.Controls.ChartControls.ChartListForecast.extendedEventArgs>' to System.EventHandler<Conwx.Net.Client.Framework.CustomerServiceReference.GetChartDataCompletedEventArgs>'
What am I doing wrong here? Alternative solutions are also welcome.
.K
As I read it, the key problem here is not being able to remove the handler; if so, all you need it to store the delegate (where in the below, YourDelegateType is meant to mean: the defined type of GetChartDataCompleted):
YourDelegateType handler = (se, ea) =>
{
_cachedCharts = ea.Result;
ChartDataRetrieved(ea.Result);
if (action != null)
action.Invoke();
_csr = null;
};
_csr.ServiceClient.GetChartDataCompleted += handler;
...
_csr.ServiceClient.GetChartDataCompleted -= handler;
You can also make it self-unsubscribing (i.e. so that it unsubscribes when the event is raised):
YourDelegateType handler = null;
handler = (se, ea) =>
{
_cachedCharts = ea.Result;
ChartDataRetrieved(ea.Result);
if (action != null)
action.Invoke();
_csr.ServiceClient.GetChartDataCompleted -= handler;
_csr = null;
};
_csr.ServiceClient.GetChartDataCompleted += handler;
No, you can't do this because it's the class which raises the GetChartDataCompleted event which creates the object passed (as a reference) to the event handler. It will be creating a GetChartDataCompletedEventArgs - not an extendedEventArgs.
If you think about it, it's like trying to implement an interface which looks like this:
public interface IFoo
{
void Foo(object x);
}
with a class like this:
public class Bar : IFoo
{
// We don't care if someone calling IFoo wants to pass us something
// other than a string - we want a string, darn it!
public void Foo(string y)
{
Console.WriteLine(y.Length);
}
}
That's clearly not going to work...
Marc has shown one approach to fixing it - but I'd also point out that you should probably actually only be removing the delegate when the event fires. I'm assuming that the fact that the method is called GetChartDataAsync means it's a non-blocking method... so unsubscribing from the event immediately after calling it probably isn't a great idea.
If you'd prefer to avoid the anonymous methods, you can manually do essentially what the compiler is doing for you under the hood. That is, create a closure class to hold the Action and a reference to itself as fields and which exposes the method you want to assign to the event. Something like this:
class RetrieveDataClosure
{
private Action action;
private MyClass self;
public RetrieveDataClosure(Action action, MyClass self)
{
this.action = action;
this.self = self;
}
public void ChartDataCompleted(object se, MyEventArgs ea)
{
self._cachedCharts = ea.Result;
self.ChartDataRetrieved(ea.Result);
if (action != null)
action.Invoke();
self._csr = null;
}
}
Which you'd use in your code like this:
var closure = new RetrieveDataClosure(action, this);
_csr = new CustomerServiceRepository();
_csr.ServiceClient.GetChartDataCompleted += closure.ChartDataCompleted;
_csr.ServiceClient.GetChartDataAsync(
Settings.Current.Customer.CustomerName,
pointId,
chartCollectionId);
_csr.ServiceClient.GetChartDataCompleted -= closure.ChartDataCompleted;

How to pass an event to a method?

I would like to create a method that takes an event as an argument and adds eventHandler to it to handle it properly. Like this:
I have two events:
public event EventHandler Click;
public event EventHandler Click2;
Now I would like to pass a particular event to my method like this (pseudocode):
public AttachToHandleEvent(EventHandler MyEvent)
{
MyEvent += Item_Click;
}
private void Item_Click(object sender, EventArgs e)
{
MessageBox.Show("lalala");
}
ToolStripMenuItem tool = new ToolStripMenuItem();
AttachToHandleEvent(tool.Click);
Is it possible?
I've noticed that this code worked fine, and returned to my project and noticed that when I pass an event declared in my class, it works, but when I pass event from other class it still does not work.
What I get is this error:
The event
'System.Windows.Forms.ToolStripItem.Click'
can only appear on the left hand side
of += or -=
My original answer was suitable from within the class that defined the event, but you've since updated your question to reflect that you wish to accomplish this from outside the defining class, so I've stricken that.
Only the class that defines an event can refer to the implicit delegate variable that the event uses. From outside that class, you only have access to the add and remove methods, via += and -=. This means that you can't do what you're asking, directly. You can, however, use a functional approach.
class A{
public event EventHandler Event1;
public void TriggerEvent1(){
if(Event1 != null)
Event1(this, EventArgs.Empty);
}
}
class B{
static void HandleEvent(object o, EventArgs e){
Console.WriteLine("Woo-hoo!");
}
static void AttachToEvent(Action<EventHandler> attach){
attach(HandleEvent);
}
static void Main(){
A a = new A();
AttachToEvent(handler=>a.Event1 += handler);
a.TriggerEvent1();
}
}
I did it like this:
public AttachToHandleEvent(Object obj, string EventName)
{
EventInfo mfi = obj.GetType().GetEvent(EventName);
MethodInfo mobj = mfi.GetAddMethod();
mobj.Invoke(obj, new object[] { Item_Click});
}
private void Item_Click(object sender, EventArgs e)
{
MessageBox.Show("lalala");
}
ToolStripMenuItem tool = new ToolStripMenuItem();
AttachToHandleEvent(tool "Click");
Thank you all for advice. This solution could not be done without your help.
It's not possible. You can use a delegate instead of an event if that meets your needs.
Just write tool.Click += Item_Click;
Edit: From MSDN "Events can only be invoked from within the class or struct where they (it) are declared". So what you are trying to do is not possible. Could you elaborate more on your needs? Why would you want to pass an event as a parameter?
delegate void doIt(object sender, object data);
event doIt OnDoIt;
void add(doIt theDel)
{
OnDoIt += theDel;
}
void doIt1(object a, object b)
{
}
void doIt2(object a, object b)
{
}
void add()
{
add(doIt1);
add(doIt2);
}
Your question suggests that you got some mechanisms wrong:
You can't pass events!
You most probably want to pass a function as a parameter, so the calling method will call that other method at some point. In technical terms this is a delegate. I suggest using the already defined Action class. Here's an example snippet:
void MyFunction (string otherArguments, Action onFinished){
...
if (onFinished != null)
onFinished.Invoke();
}
The nice thing about this is that when calling MyFunction you can declare the Action using the inline syntax:
MyFunction("my other argument", ()=>{
///do stuff here, which will be execuded when the action is invoked
});
I pass functions/methods (instead of events) like this:
class A
{
public void something()
{
var myAction =
new Action<object, object>((sender, evArgs) => {
MessageBox.Show("hiii, event happens " + (evArgs as as System.Timers.ElapsedEventArgs).SignalTime);
});
B.timer(myAction);
}
}
class B
{
public static void timer( Action<object, System.Timers.ElapsedEventArgs> anyMethod)
{
System.Timers.Timer myTimer = new System.Timers.Timer();
myTimer.Elapsed += new System.Timers.ElapsedEventHandler(anyMethod);
myTimer.Interval = 2000;
myTimer.Start();
}
}
Giving an update to this question with an object oriented solution.
Instead of using an Action<EventHandler> that registers the event, you could create an object handling that for you
public class AEvent
{
private readonly A aInstance;
private AEvent(A instance) {
aInstance = instance;
}
public void Add(EventHandler eventHandler)
=> a.Event1 += eventHandler;
public void Remove(EventHandler eventHandler)
=> a.Event1 -= eventHandler;
public EventHandler Invoke => aInstance.Event1;
}
Then later on use that object like this:
static void Main(){
A a = new A();
AEvent aEvent = new AEvent(A)
aEvent.Add(handler);
a.Invoke();
}
One approach I haven't seen here would be to create an object which has delegates for subscribe and unsubscribe. Here is a complete example program.
class Program
{
private event EventHandler<EventArgs> eventHandler;
public static void Main(string[] args)
{
Program program = new Program();
Thing thing = new Thing(new EventWrapper<EventArgs>(
delegate(EventHandler<EventArgs> handler) { program.eventHandler += handler; },
delegate(EventHandler<EventArgs> handler) { program.eventHandler -= handler; }
));
// events are fired
program.eventHandler?.Invoke(program, EventArgs.Empty);
thing.Unsubscribe();
}
}
class Thing
{
private readonly Action<EventHandler<EventArgs>> _unsubscribeEventHandler;
public Thing(EventWrapper<EventArgs> eventHandler)
{
this._unsubscribeEventHandler = eventHandler.Unsubscribe;
eventHandler.Subscribe?.Invoke(OnEvent);
Console.WriteLine("subscribed");
}
private void OnEvent(object? sender, EventArgs e)
{
Console.WriteLine("event fired");
}
public void Unsubscribe()
{
_unsubscribeEventHandler?.Invoke(OnEvent);
Console.WriteLine("unsubscribed");
}
}
class EventWrapper<T> where T : EventArgs
{
public Action<EventHandler<T>> Subscribe { get; private set; }
public Action<EventHandler<T>> Unsubscribe { get; private set; }
public EventWrapper(Action<EventHandler<T>> subscribe, Action<EventHandler<T>> unsubscribe)
{
Subscribe = subscribe;
Unsubscribe = unsubscribe;
}
}
In this example, we created a new class called EventWrapper<T> which wraps delegates for += and -= and exposes them with Subscribe and Unsubscribe methods. The delegates will need to be created by the class which created the event.

Categories

Resources