I've a library that defines ConfigurationChanged event. It used to use EventArgs, but I want to extend it to MyEventArgs, but don't want to bring BC break to customers. They shall still be able to have EventArgs signature to consume the event args.
It works fine as long as they assign their handling method directly. But If some of them pass EventHandler<EventArgs> around, then it won't assign with
Cannot implicitly convert type 'System.EventHandler<System.EventArgs>' to 'System.EventHandler<MyEventArgs>'.
Code snippet
public class Test
{
private static event EventHandler<MyEventArgs> ConfigurationChanged;
public Test(EventHandler<EventArgs> eventHandler)
{
ConfigurationChanged += eventHandler; // Cannot implicitly convert
ConfigurationChanged += OnConfigurationChanged; // Works
}
private static void OnConfigurationChanged(object sender, EventArgs e)
{
}
}
public class MyEventArgs : EventArgs
{
}
Would you have any workaround/best practice around that or there is no way to obey breaking change?
Changing the type of the event is a binary breaking change whatever you do. What you've described is trying to make it not a source breaking change.
What you can do is add the more specific event as a separate event, and proxy event subscription by creating a new EventHandler<MyEventArgs> from the handler that's passed to the add/remove parts. Fortunately, delegate equality still works in that situation, so unsubscribing does the right thing automatically:
using System;
public class MyEventArgs : EventArgs
{
}
public class Test
{
public event EventHandler<EventArgs> GeneralEvent
{
add => SpecificEvent += new EventHandler<MyEventArgs>(value);
remove => SpecificEvent -= new EventHandler<MyEventArgs>(value);
}
public event EventHandler<MyEventArgs> SpecificEvent;
private void OnEvent(MyEventArgs args)
{
SpecificEvent?.Invoke(this, args);
}
public static void Main()
{
var test = new Test();
EventHandler<EventArgs> generalHandler = (sender, args) => Console.WriteLine("General");
EventHandler<MyEventArgs> specificHandler = (sender, args) => Console.WriteLine("Specific");
test.GeneralEvent += generalHandler;
test.SpecificEvent += specificHandler;
Console.WriteLine("Raising event with both subscribed");
test.OnEvent(new MyEventArgs());
test.GeneralEvent -= generalHandler;
test.SpecificEvent -= specificHandler;
Console.WriteLine("Raising event with both unsubscribed");
test.OnEvent(new MyEventArgs());
}
}
Related
I'm used to using delegate EventHandler for event callbacks but now I'm attempting to use event Action for invoking events. I couldn't find much info on how this can be used properly anywhere so I'm hoping someone can point me in the right direction.
I have an Action event handler that handles string objects. In my subscriber class I have public event Action<string> UploadProgress;. The event handler is invoked like this:
UploadProgress.Invoke(string.Format("sending file data {0:0.000}%", (bytesSoFar * 100.0f) / totalToUpload));
The listener class is subscribed to this event as below:
uploadFile.UploadProgress += uploadFile_UploadProgress;
void uploadFile_UploadProgress(string obj)
{
var prog = obj;
}
When the event is invoked, I get System.NullReferenceException: Object reference not set to an instance of an object. I'm not sure what I need to change in the subscriber class to avoid this error. Can someone tell me the proper way to use event Action or provide me the link to where I can read up on it? I know how to use the normal Action but confused about declaring it as an event. Any help is appreciated.
This way is much better, send bytesToUpload and totalToUpload through event, instead of the whole Action (right sample):
internal class Program
{
private static void Main(string[] args)
{
SomeClass someClass = new SomeClass();
someClass.UploadProgress += SomeClass_UploadProgress;
someClass.DoSomeUpload();
}
private static void SomeClass_UploadProgress(object sender, UploadEventArgs e)
{
string s = string.Format("sending file data {0:0.000}%", (e.BytesSoFar * 100.0f) / e.TotalToUpload);
Console.WriteLine(s);
}
}
public class UploadEventArgs : EventArgs
{
public float BytesSoFar { get; set; }
public float TotalToUpload { get; set; }
}
public class SomeClass
{
public event EventHandler<UploadEventArgs> UploadProgress;
public void DoSomeUpload()
{
if (UploadProgress != null)
{
UploadEventArgs e = new UploadEventArgs();
e.BytesSoFar = 123f;
e.TotalToUpload = 100000f;
UploadProgress.Invoke(this, e);
}
}
}
Is it possible to send reference of 'sender' without specifying it explicitly as a parameter in delegate-based event handling?
I have a internal class which raises some events and I want to call the events explicitly for test purposes.
public class Manager {
public class DataStruct {
public int Id { get; private set; }
public event EventHandler Event1; // Can't be called by other classes
public void fireEvent1(Event1();} // So another caller...
// Delegates *can* be called by other classes
public delegate void DelegateHandler(DataStruct sender);
public DelegateHandler NewEvent;
public void DelegateHandler(DataStruct sender) {
MessageBox.Show(string.Format(
"{0} raises event", sender.Id));
}
}
}
// Form1 ///////////////////////////////////////////////////////////////////
partial class Form1 {
Manager.DataStruct dsRaiser, dsListener;
private void Form1_Load(object sender, EventArgs e) {
dsRaiser.Event1 += dsListener.SOME_HANDLER;
dsRaiser.NewEvent += dsListener.DelegateHandler;
}
private void button1_Click(object sender, ...) {
dsRaiser.fireEvent1(); // No argument needed but fireEvent1, not Event1().
}
private void button2_Click(object sender, ...) {
dsRaiser.NewEvent(dsRaiser); // Way to omit parameter dsRaiser?
}
//////////////////////////////////////////////////////////////////////////
If your handler method needs to use the sender's reference, then you HAVE to pass that reference.
If not, just declare a void parameterless delegate, like Action.
But when thinking of events, that parameter should be passed by the class that raises the event itself. (Remember events are not meant to be called from outside).
So, if you really want to use a simple delegate instead of an event, you will have to pass the parameters.
If you need the sender, you will need to do exactly what you did with the event: create a method to "raise" the delegate, and in that method you pass this as the sender.
But considering you have to do exactly the same thing in both cases, I'd surely use the event.
public class DataStruct {
public int Id { get; private set; }
public event EventHandler Event1; // Can't be called by other classes
// you need to pass those parameters to the event when called.
public void fireEvent1{Event1(this, new EventArgs());}
// Delegates *can* be called by other classes, but only with all parameters passed.
public delegate void DelegateHandler(DataStruct sender);
public DelegateHandler NewEvent;
// To avoid passing parameters, you need to do exactly what you did with the event
public void RaiseDelegate() { NewEvent(this); }
public void DelegateHandler(DataStruct sender) {
MessageBox.Show(string.Format(
"{0} raises event", sender.Id));
}
}
Yes, it is possible.
Just store the sender inside the subscription when subscribing to an event.
If we had a delegate declared like this:
public Action NewEvent; // No need to be DelegateHandler
Then we can use c# compiler to generate such a subscription for us using anonymous delegates:
dsRaiser.NewEvent += () =>
{
dsListener.DelegateHandler(dsRaiser);
};
Anything, which we reference inside our anonymous handler is automatically captured for us (both dbListener and dsRaiser in this case).
Or, if we want explicit declaration of the subscription:
class MySubscription
{
private readonly DataStruct _raiser;
private readonly DataStruct _listener;
public MySubscription(DataStruct raiser, DataStruct listener)
{
_raiser = raiser;
_listener = listener;
}
public void HandleTheSubscription()
{
_listener.DelegateHandler(_raiser);
}
}
And this is how we subscribe:
private void Form1_Load(object sender, EventArgs e) {
var mySubscription = new MySubscription(dsRaiser,dsListener);
dsRaiser.NewEvent += mySubscription.HandleTheSubscription;
}
As you can see MySubscription is defined by us and we can store any objects there.
I'm trying to create a reusable method for a more complicated event execution. I can't get it to compile or run with framework events that don't follow the EventHandler<Type> pattern. I would like to avoid reflection if possible as it will be a heavily used event.
I've created a test console app below which illustrates the problem:
using System;
using System.Collections.Specialized;
namespace CallEventsViaMethod
{
public class TestEventArgs : EventArgs { }
class Program
{
static void Main(string[] args)
{
MyProgram program = new MyProgram();
program.Go();
Console.ReadKey(false);
}
}
public class MyProgram
{
public event EventHandler<TestEventArgs> TestEvent;
public event NotifyCollectionChangedEventHandler CollectionChangedEvent;
public void Go()
{
TestEvent += new EventHandler<TestEventArgs>(MyProgram_TestEvent);
CollectionChangedEvent += new NotifyCollectionChangedEventHandler(MyProgram_CollectionChangedEvent);
// Want a reusable method I can use to conditionally execute any event
GeneralEventExecutor.Execute<TestEventArgs>(TestEvent, new Object(), new TestEventArgs());
GeneralEventExecutor.Execute<NotifyCollectionChangedEventArgs>(TestEvent, new Object(), new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
void MyProgram_TestEvent(object arg1, TestEventArgs arg2)
{
Console.WriteLine("Custom event ran");
}
void MyProgram_CollectionChangedEvent(object sender, NotifyCollectionChangedEventArgs e)
{
Console.WriteLine("NotifyCollectionChangedEventHandler event ran");
}
}
public static class GeneralEventExecutor
{
public static void Execute<T>(EventHandler<T> eventToRaise, object sender, T eventArgs) where T : EventArgs
{
if (eventToRaise == null)
return;
Delegate[] registeredEventHandlers = eventToRaise.GetInvocationList();
foreach (EventHandler<T> eventHandler in registeredEventHandlers)
{
object target = eventHandler.Target; // Need access to the Target property
// * Code deciding if should invoke the event handler *
eventHandler.Invoke(sender, eventArgs);
}
}
}
}
Error messages are:
error CS1502: The best overloaded method match for
'CallEventsViaMethod.GeneralEventExecutor.Execute(System.EventHandler,
object,
System.Collections.Specialized.NotifyCollectionChangedEventArgs)' has
some invalid arguments
error CS1503: Argument 1: cannot convert from
'System.EventHandler' to
'System.EventHandler'
I understand why I'm getting the error, but can't figure out a way round it.
Replace your your generic Execute<T> as below
public static void Execute<T>(Delegate eventToRaise, object sender, T eventArgs) where T:EventArgs
{
if (eventToRaise == null)
return;
Delegate[] registeredEventHandlers = eventToRaise.GetInvocationList();
foreach (Delegate eventHandler in registeredEventHandlers)
{
object target = eventHandler.Target; // Need access to the Target property for conditions
// * Code deciding if should invoke the event handler *
eventHandler.DynamicInvoke(sender, eventArgs);
}
}
I find myself doing this sort of thing quite often:-
EventHandler eh = null; //can't assign lambda directly since it uses eh
eh = (s, args) =>
{
//small snippet of code here
((SomeType)s).SomeEvent -= eh;
}
variableOfSomeType.SomeEvent += eh;
Basically I only want to attach an event handler to listen for one shot from the event, I no longer want to stay attached after that. Quite often that "snippert of code" is just one line.
My mind is going a bit numb, I'm sure there must be something I can do so I don't need to repeat all this overhead. Bear in mind that EventHandler may well be EventHandler<T>.
Any ideas how I can tidy up the repeative part of the code and just leave the snippet in a Lambda?
You could attache a permanent event handler to the event. The event handler then invokes "one shot event handlers" that are added to an internal queue:
OneShotHandlerQueue<EventArgs> queue = new OneShotHandlerQueue<EventArgs>();
Test test = new Test();
// attach permanent event handler
test.Done += queue.Handle;
// add a "one shot" event handler
queue.Add((sender, e) => Console.WriteLine(e));
test.Start();
// add another "one shot" event handler
queue.Add((sender, e) => Console.WriteLine(e));
test.Start();
Code:
class OneShotHandlerQueue<TEventArgs> where TEventArgs : EventArgs {
private ConcurrentQueue<EventHandler<TEventArgs>> queue;
public OneShotHandlerQueue() {
this.queue = new ConcurrentQueue<EventHandler<TEventArgs>>();
}
public void Handle(object sender, TEventArgs e) {
EventHandler<TEventArgs> handler;
if (this.queue.TryDequeue(out handler) && (handler != null))
handler(sender, e);
}
public void Add(EventHandler<TEventArgs> handler) {
this.queue.Enqueue(handler);
}
}
Test class:
class Test {
public event EventHandler Done;
public void Start() {
this.OnDone(new EventArgs());
}
protected virtual void OnDone(EventArgs e) {
EventHandler handler = this.Done;
if (handler != null)
handler(this, e);
}
}
You can use reflection:
public static class Listener {
public static void ListenOnce(this object eventSource, string eventName, EventHandler handler) {
var eventInfo = eventSource.GetType().GetEvent(eventName);
EventHandler internalHandler = null;
internalHandler = (src, args) => {
eventInfo.RemoveEventHandler(eventSource, internalHandler);
handler(src, args);
};
eventInfo.AddEventHandler(eventSource, internalHandler);
}
public static void ListenOnce<TEventArgs>(this object eventSource, string eventName, EventHandler<TEventArgs> handler) where TEventArgs : EventArgs {
var eventInfo = eventSource.GetType().GetEvent(eventName);
EventHandler<TEventArgs> internalHandler = null;
internalHandler = (src, args) => {
eventInfo.RemoveEventHandler(eventSource, internalHandler);
handler(src, args);
};
eventInfo.AddEventHandler(eventSource, internalHandler);
}
}
Use it like so:
variableOfSomeType.ListenOnce("SomeEvent",
(s, args) => Console.WriteLine("I should print only once!"));
variableOfSomeType.ListenOnce<InterestingEventArgs>("SomeOtherEvent",
(s, args) => Console.WriteLine("I should print only once!"));
If you can use the Reactive Extensions for .NET, you can simplify this.
You can make an Observable from an event, and only listen for the first element using .Take(1), to do your small snippet of code. This turns this entire process into a couple of lines of code.
Edit: In order to demonstrate, I've made a full sample program (I'll paste below).
I moved the observable creation and subscription into a method (HandleOneShot). This lets you do what you're attempting with a single method call. For demonstrating, I made a class with two properties that implements INotifyPropertyChanged, and am listening for the first property changed event, writing to the console when it occurs.
This takes your code, and changes it to:
HandleOneShot<SomeEventArgs>(variableOfSomeType, "SomeEvent", e => {
// Small snippet of code here
});
Notice that all of the subscription/unsubscription happens automatically for you behind the scenes. There's no need to handle putting in the subscription manually - just Subscribe to the Observable, and Rx takes care of this for you.
When run, this code prints:
Setup...
Setting first property...
**** Prop2 Changed! /new val
Setting second property...
Setting first property again.
Press ENTER to continue...
You only get a single, one shot trigger of your event.
namespace ConsoleApplication1
{
using System;
using System.ComponentModel;
using System.Linq;
class Test : INotifyPropertyChanged
{
private string prop2;
private string prop;
public string Prop
{
get {
return prop;
}
set
{
if (prop != value)
{
prop = value;
if (PropertyChanged!=null)
PropertyChanged(this, new PropertyChangedEventArgs("Prop"));
}
}
}
public string Prop2
{
get
{
return prop2;
}
set
{
if (prop2 != value)
{
prop2 = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Prop2"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
class Program
{
static void HandleOneShot<TEventArgs>(object target, string eventName, Action<TEventArgs> action) where TEventArgs : EventArgs
{
var obsEvent = Observable.FromEvent<TEventArgs>(target, eventName).Take(1);
obsEvent.Subscribe(a => action(a.EventArgs));
}
static void Main(string[] args)
{
Test test = new Test();
Console.WriteLine("Setup...");
HandleOneShot<PropertyChangedEventArgs>(
test,
"PropertyChanged",
e =>
{
Console.WriteLine(" **** {0} Changed! {1}/{2}!", e.PropertyName, test.Prop, test.Prop2);
});
Console.WriteLine("Setting first property...");
test.Prop2 = "new value";
Console.WriteLine("Setting second property...");
test.Prop = "second value";
Console.WriteLine("Setting first property again...");
test.Prop2 = "other value";
Console.WriteLine("Press ENTER to continue...");
Console.ReadLine();
}
}
}
Another user encountered a very similar problem, and I believe the solution in that thread applies here.
In particular, what you have is not an instance of the publish/subscribe pattern, its a message queue. Its easy enough to create your own message queue using a Queue{EventHandler}, where you dequeue events as you invoke them.
So instead of hooking on to an event handler, your "one-shot" events should expose a method allowing clients to add an function to the message queue.
Does it work? If so, then I say go for it. For a one-shot event that looks to be quite elegant.
What I like...
If s is garbage collected, so will the event handler.
The detaching code is right next to the attaching code, making it easy to see what you are are doing.
You might be able to generalize it, but I'm not entierly sure how to because I can't seem to get a pointer to a event.
Personally, I just create a specialized extension method for whatever type has the event I'm dealing with.
Here's a basic version of something I am using right now:
namespace MyLibrary
{
public static class FrameworkElementExtensions
{
public static void HandleWhenLoaded(this FrameworkElement el, RoutedEventHandler handler)
{
RoutedEventHandler wrapperHandler = null;
wrapperHandler = delegate
{
el.Loaded -= wrapperHandler;
handler(el, null);
};
el.Loaded += wrapperHandler;
}
}
}
The reason I think this is the best solution is because you often don't need to just handle the event one time. You also often need to check if the event has already passed... For instance, here is another version of the above extension method that uses an attached property to check if the element is already loaded, in which case it just calls the given handler right away:
namespace MyLibraryOrApplication
{
public static class FrameworkElementExtensions
{
public static void HandleWhenLoaded(this FrameworkElement el, RoutedEventHandler handler)
{
if ((bool)el.GetValue(View.IsLoadedProperty))
{
// el already loaded, call the handler now.
handler(el, null);
return;
}
// el not loaded yet. Attach a wrapper handler that can be removed upon execution.
RoutedEventHandler wrapperHandler = null;
wrapperHandler = delegate
{
el.Loaded -= wrapperHandler;
el.SetValue(View.IsLoadedProperty, true);
handler(el, null);
};
el.Loaded += wrapperHandler;
}
}
}
You probably want to work with the new async/await idioms.
Usually when I need to execute an event handler one-shot like you described, what I really need is something like:
await variableOfSomeSort.SomeMethodAsync();
//small snippet of code here
Why not do use the delegate stack built into the event?
Something like...
private void OnCheckedIn(object sender, Session e)
{
EventHandler<Session> nextInLine = null;
lock (_syncLock)
{
if (SessionCheckedIn != null)
{
nextInLine = (EventHandler<Session>)SessionCheckedIn.GetInvocationList()[0];
SessionCheckedIn -= nextInLine;
}
}
if ( nextInLine != null )
{
nextInLine(this, e);
}
}
Is it possible to unsubscribe an anonymous method from an event?
If I subscribe to an event like this:
void MyMethod()
{
Console.WriteLine("I did it!");
}
MyEvent += MyMethod;
I can un-subscribe like this:
MyEvent -= MyMethod;
But if I subscribe using an anonymous method:
MyEvent += delegate(){Console.WriteLine("I did it!");};
is it possible to unsubscribe this anonymous method? If so, how?
Action myDelegate = delegate(){Console.WriteLine("I did it!");};
MyEvent += myDelegate;
// .... later
MyEvent -= myDelegate;
Just keep a reference to the delegate around.
One technique is to declare a variable to hold the anonymous method which would then be available inside the anonymous method itself. This worked for me because the desired behavior was to unsubscribe after the event was handled.
Example:
MyEventHandler foo = null;
foo = delegate(object s, MyEventArgs ev)
{
Console.WriteLine("I did it!");
MyEvent -= foo;
};
MyEvent += foo;
Since C# 7.0 local functions feature has been released, the approach suggested by J c becomes really neat.
void foo(object s, MyEventArgs ev)
{
Console.WriteLine("I did it!");
MyEvent -= foo;
};
MyEvent += foo;
So, honestly, you do not have an anonymous function as a variable here. But I suppose the motivation to use it in your case can be applied to local functions.
From memory, the specification explicitly doesn't guarantee the behaviour either way when it comes to equivalence of delegates created with anonymous methods.
If you need to unsubscribe, you should either use a "normal" method or retain the delegate somewhere else so you can unsubscribe with exactly the same delegate you used to subscribe.
In 3.0 can be shortened to:
MyHandler myDelegate = ()=>Console.WriteLine("I did it!");
MyEvent += myDelegate;
...
MyEvent -= myDelegate;
Instead of keeping a reference to any delegate you can instrument your class in order to give the event's invocation list back to the caller. Basically you can write something like this (assuming that MyEvent is declared inside MyClass):
public class MyClass
{
public event EventHandler MyEvent;
public IEnumerable<EventHandler> GetMyEventHandlers()
{
return from d in MyEvent.GetInvocationList()
select (EventHandler)d;
}
}
So you can access the whole invocation list from outside MyClass and unsubscribe any handler you want. For instance:
myClass.MyEvent -= myClass.GetMyEventHandlers().Last();
I've written a full post about this tecnique here.
Kind of lame approach:
public class SomeClass
{
private readonly IList<Action> _eventList = new List<Action>();
...
public event Action OnDoSomething
{
add {
_eventList.Add(value);
}
remove {
_eventList.Remove(value);
}
}
}
Override the event add/remove methods.
Keep a list of those event handlers.
When needed, clear them all and re-add the others.
This may not work or be the most efficient method, but should get the job done.
If you want to be able to control unsubscription then you need to go the route indicated in your accepted answer. However, if you are just concerned about clearing up references when your subscribing class goes out of scope, then there is another (slightly convoluted) solution which involves using weak references. I've just posted a question and answer on this topic.
One simple solution:
just pass the eventhandle variable as parameter to itself.
Event if you have the case that you cannot access the original created variable because of multithreading, you can use this:
MyEventHandler foo = null;
foo = (s, ev, mehi) => MyMethod(s, ev, foo);
MyEvent += foo;
void MyMethod(object s, MyEventArgs ev, MyEventHandler myEventHandlerInstance)
{
MyEvent -= myEventHandlerInstance;
Console.WriteLine("I did it!");
}
If the best way is to keep a reference on the subscribed eventHandler, this can be achieved using a Dictionary.
In this example, I have to use a anonymous method to include the mergeColumn parameter for a set of DataGridViews.
Using the MergeColumn method with the enable parameter set to true enables the event while using it with false disables it.
static Dictionary<DataGridView, PaintEventHandler> subscriptions = new Dictionary<DataGridView, PaintEventHandler>();
public static void MergeColumns(this DataGridView dg, bool enable, params ColumnGroup[] mergedColumns) {
if(enable) {
subscriptions[dg] = (s, e) => Dg_Paint(s, e, mergedColumns);
dg.Paint += subscriptions[dg];
}
else {
if(subscriptions.ContainsKey(dg)) {
dg.Paint -= subscriptions[dg];
subscriptions.Remove(dg);
}
}
}
if you want refer to some object with this delegate, may be you can use Delegate.CreateDelegate(Type, Object target, MethodInfo methodInfo)
.net consider the delegate equals by target and methodInfo
There is a way to solve this by implementing the closure yourself instead of a lambda expression.
Assume that the class to be used as a capture variable is as follows.
public class A
{
public void DoSomething()
{
...
}
}
public class B
{
public void DoSomething()
{
...
}
}
public class C
{
public void DoSomething()
{
...
}
}
These classes will be used as capture variables, so we instantiate them.
A a = new A();
B b = new B();
C c = new C();
Implement the closure class as shown below.
private class EventHandlerClosure
{
public A a;
public B b;
public C c;
public event EventHandler Finished;
public void MyMethod(object, MyEventArgs args)
{
a.DoSomething();
b.DoSomething();
c.DoSomething();
Console.WriteLine("I did it!");
Finished?.Invoke(this, EventArgs.Empty);
}
}
Instantiate the closure class, create a handler, then subscribe to the event and subscribe to the lambda expression that unsubscribes from the closure class's Finished event.
var closure = new EventHandlerClosure
{
a = a,
b = b,
c = c
};
var handler = new MyEventHandler(closure.MyMethod);
MyEvent += handler;
closure.Finished += (s, e)
{
MyEvent -= handler;
}
I discovered this quite old thread recently for a C# project and found all the answers very useful. However, there was one aspect that didn't work well for my particular use case - they all put the burden of unsubscribing from an event on the subscriber. I understand that one could make the argument that it's the subscribers job to handle this, however that isn't realistic for my project.
My primary use case for events is for listening to timers to sequence animations (it's a game). In this scenario, I use a lot of anonymous delegates to chain together sequences. Storing a reference to these isn't very practical.
In order to solve this, I've created a wrapper class around an event that lets you subscribe for a single invocation.
internal class EventWrapper<TEventArgs> {
private event EventHandler<TEventArgs> Event;
private readonly HashSet<EventHandler<TEventArgs>> _subscribeOnces;
internal EventWrapper() {
_subscribeOnces = new HashSet<EventHandler<TEventArgs>>();
}
internal void Subscribe(EventHandler<TEventArgs> eventHandler) {
Event += eventHandler;
}
internal void SubscribeOnce(EventHandler<TEventArgs> eventHandler) {
_subscribeOnces.Add(eventHandler);
Event += eventHandler;
}
internal void Unsubscribe(EventHandler<TEventArgs> eventHandler) {
Event -= eventHandler;
}
internal void UnsubscribeAll() {
foreach (EventHandler<TEventArgs> eventHandler in Event?.GetInvocationList()) {
Event -= eventHandler;
}
}
internal void Invoke(Object sender, TEventArgs e) {
Event?.Invoke(sender, e);
if(_subscribeOnces.Count > 0) {
foreach (EventHandler<TEventArgs> eventHandler in _subscribeOnces) {
Event -= eventHandler;
}
_subscribeOnces.Clear();
}
}
internal void Remove() {
UnsubscribeAll();
_subscribeOnces.Clear();
}
}
The side benefit of having this in a class is that you can make it private and expose only the functionality you want. For example, only expose the SubscribeOnce (and not the Subscribe) method.
public class MyClass {
private EventWrapper<MyEventEventArgs> myEvent = new EventWrapper<MyEventEventArgs>();
public void FireMyEvent() {
myEvent.Invoke(this, new MyEventEventArgs(1000, DateTime.Now));
}
public void SubscribeOnce(EventHandler<MyEventEventArgs> eventHandler) {
myEvent.SubscribeOnce(eventHandler);
}
public class MyEventEventArgs : EventArgs {
public int MyInt;
public DateTime MyDateTime;
public MyEventEventArgs(int myInt, DateTime myDateTime) {
MyInt = myInt;
MyDateTime = myDateTime;
}
}
}
The tradeoff here is more overhead for having an instance of this for each event, however in my scenario - this is an acceptable tradeoff to ensure that garbage gets collected efficiently and the code is more maintainable on the subscriber side. Full example here.
Here is a simple solution, which removes all assigned methods from an event. Also anonymous methods.
Use this code and adjust the names.
if (MyEvent != null)
foreach (Delegate del in MyEvent.GetInvocationList())
MyEvent -= (EventHandler<MyEventHandlerType>)del;
Example usage
public class SomeClass
{
public event EventHandler<NiceEventArgs> NiceEvent;
public void RemoveHandlers()
{
if (NiceEvent != null)
foreach (Delegate del in NiceEvent.GetInvocationList())
NiceEvent -= (EventHandler<NiceEventArgs>)del;
}
}
Thanks to hemme's answer, which I used as inspiration.