I want to be able to find out if an event is hooked up or not. I've looked around, but I've only found solutions that involved modifying the internals of the object that contains the event. I don't want to do this.
Here is some test code that I thought would work:
// Create a new event handler that takes in the function I want to execute when the event fires
EventHandler myEventHandler = new EventHandler(myObject_SomeEvent);
// Get "p1" number events that got hooked up to myEventHandler
int p1 = myEventHandler.GetInvocationList().Length;
// Now actually hook an event up
myObject.SomeEvent += m_myEventHandler;
// Re check "p2" number of events hooked up to myEventHandler
int p2 = myEventHandler.GetInvocationList().Length;
Unfort the above is dead wrong. I thought that somehow the "invocationList" in myEventHandler would automatically get updated when I hooked an event to it. But no, this is not the case. The length of this always comes back as one.
Is there anyway to determine this from outside the object that contains the event?
If the object concerned has specified the event keyword, then the only things you can do are add (+=) and remove (-=) handlers, nothing more.
I believe that comparing the invocation list length would work, but you need to be operating inside the object to get at it.
Also, keep in mind that the += and -= operators return a new event object; they don't modify an existing one.
Why do you want to know if a particular event is hooked up? Is it to avoid registering multiple times?
If so, the trick is to remove the handler first (-=) as removing a handler that's not there is legal, and does nothing. Eg:
// Ensure we don't end up being triggered multiple times by the event
myObject.KeyEvent -= KeyEventHandler;
myObject.KeyEvent += KeyEventHandler;
There is a subtle illusion presented by the C# event keyword and that is that an event has an invocation list.
If you declare the event using the C# event keyword, the compiler will generate a private delegate in your class, and manage it for you. Whenever you subscribe to the event, the compiler-generated add method is invoked, which appends the event handler to the delegate's invocation list. There is no explicit invocation list for the event.
Thus, the only way to get at the delegate's invocation list is to preferably:
Use reflection to access the compiler-generated delegate OR
Create a non-private delegate (perhaps internal) and implement the event's add/remove methods manually (this prevents the compiler from generating the event's default implementation)
Here is an example demonstrating the latter technique.
class MyType
{
internal EventHandler<int> _delegate;
public event EventHandler<int> MyEvent;
{
add { _delegate += value; }
remove { _delegate -= value; }
}
}
It can be done, but it takes some hackery... as mentioned above the compiler generates the implementation of the event, including its backing field. Reflection lets you retrieve the backing field by name, and once you have access to it you can call GetInvocationList() even though you're outside the class itself.
Since you're asking to use reflection to get the event by name I assume you're also using reflection to get the Type by name--I'm whipping up an example that will show how to do it.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Reflection;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string typeName = "ConsoleApplication1.SomeClass, ConsoleApplication1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null";
string eventName = "SomeEvent";
Type declaringType = Type.GetType(typeName);
object target = Activator.CreateInstance(declaringType);
EventHandler eventDelegate;
eventDelegate = GetEventHandler(target, eventName);
if (eventDelegate == null) { Console.WriteLine("No listeners"); }
// attach a listener
SomeClass bleh = (SomeClass)target;
bleh.SomeEvent += delegate { };
//
eventDelegate = GetEventHandler(target, eventName);
if (eventDelegate == null)
{
Console.WriteLine("No listeners");
}
else
{
Console.WriteLine("Listeners: " + eventDelegate.GetInvocationList().Length);
}
Console.ReadKey();
}
static EventHandler GetEventHandler(object classInstance, string eventName)
{
Type classType = classInstance.GetType();
FieldInfo eventField = classType.GetField(eventName, BindingFlags.GetField
| BindingFlags.NonPublic
| BindingFlags.Instance);
EventHandler eventDelegate = (EventHandler)eventField.GetValue(classInstance);
// eventDelegate will be null if no listeners are attached to the event
if (eventDelegate == null)
{
return null;
}
return eventDelegate;
}
}
class SomeClass
{
public event EventHandler SomeEvent;
}
}
You should be able to get the invocation list via the "event". Roughly, it will be something like..
public delegate void MyHandler;
public event MyHandler _MyEvent
public int GetInvocationListLength()
{
var d = this._MyEvent.GetInvocationList(); //Delegate[]
return d.Length;
}
I used your example and modified it a little bit. registering an event handler increases the number of invocations. even when using two different callback methods (as shown here) or using the same callback method.
private void SomeMethod()
{
// Create a new event handler that takes in the function I want to execute when the event fires
var myEventHandler = new EventHandler(OnPropertyChanged);
// Get "p1" number events that got hooked up to myEventHandler
int p1 = myEventHandler.GetInvocationList().Length; // 1
// Now actually hook an event up
myEventHandler += OnPropertyChanged2;
// Re check "p2" number of events hooked up to myEventHandler
int p2 = myEventHandler.GetInvocationList().Length; // 2
myEventHandler.Invoke(null, null);
// each of the registered callback methods are executed once.
// or if the same callback is used, then twice.
}
private void OnPropertyChanged2(object? sender, EventArgs e)
{}
private void OnPropertyChanged(object? sender, EventArgs e)
{}
As others already mentioned, the access to eventhandler.GetInvocationList is limited to the class itself, you need to expose a property or method to retrieve the delegate list.
Like this:
protected Delegate[]? GetInvocations() => PropertyChanged?.GetInvocationList();
depending on your usage make it protected, internal or both.
Related
I need to copy the subscribers of one event to another event. Can I get the subscribers of an event (like MyEvent[0] returning a delegate)?
If this is not possible I would use the add accessor to add the delegates to a list. Would that be the best solution?
C# events/delegates are multicast, so the delegate is itself a list. From within the class, to get individual callers, you can use:
if (field != null)
{
// or the event-name for field-like events
// or your own event-type in place of EventHandler
foreach(EventHandler subscriber in field.GetInvocationList())
{
// etc
}
}
However, to assign all at once, just use += or direct assignment:
SomeType other = ...
other.SomeEvent += localEvent;
If the event is one published by another class, you can't - at least, not reliably. While we often think of an event as being just a delegate variable, it's actually just a pair of methods: add and remove (or subscribe and unsubscribe).
If it's your own code that's publishing the event, it's easy - you can make the add/remove accessors do whatever you like.
Have a look at my article on events and see if that helps you. If not, please give more details about what you want to do, specifying which bits of code you're able to modify and which you aren't.
In case you need to examine subscribers of an external class' event:
EventHandler e = typeof(ExternalClass)
.GetField(nameof(ExternalClass.Event), BindingFlags.Instance | BindingFlags.NonPublic)
.GetValue(instanceOfExternalClass) as EventHandler;
if (e != null)
{
Delegate[] subscribers = e.GetInvocationList();
}
Update (thanks to commenters): delegate immutability means that cloning achieves nothing over an assignment.
When one writes:
myDelegate += AHandler
A completely new delegate instance is created and assigned to myDelegate.
Therefore, the code below would work exactly the same without the Clone call.
MulticastDelegate (the underlying type) has a Clone method.
To be able to get to the underlying delegate you might need to avoid the usual helper that the event keyword generates, and manage things directly (custom add and remove accessors).
To show this:
public class Program {
public delegate void MyDelegate(string name);
public event MyDelegate EventOne;
public void HandlerOne(string name) => Console.WriteLine($"This is handler one: {name}");
public void HandlerTwo(string name) => Console.WriteLine($"This is handler two: {name}");
public void HandlerThree(string name) => Console.WriteLine($"This is handler three: {name}");
public void Run() {
EventOne += HandlerOne;
EventOne += HandlerTwo;
Console.WriteLine("Before clone");
EventOne("EventOne");
MyDelegate eventTwo = (MyDelegate)EventOne.Clone();
MyDelegate eventTwo = EventOne;
Console.WriteLine("After clone copy");
EventOne("EventOne");
eventTwo("eventTwo");
Console.WriteLine("Change event one to show it is different");
EventOne += HandlerThree;
EventOne("EventOne");
eventTwo("eventTwo");
}
private static void Main(string[] args) => (new Program()).Run();
}
The following is an example from MSN website. This is a good example, I just do not understand what this line:
EventHandler<CustomEventArgs> handler = RaiseCustomEvent;
is doing?
Is not RaiseCustomEvent an event based on the definition at the top of the program?
Why is an Event is equated to an EventHandler? These are two different types.
Where is RaiseCustomEvent being initialized? If it is not initialized how can we copy it or why do we want to copy something uninitialized to something else?
What is the handler variable there for? Is that an event or an event handler?
I'm very confused and am trying hard to get this event/event handler/delegate issue understood.
Here is the sample code from MSN
namespace DotNetEvents
{
using System;
using System.Collections.Generic;
// Define a class to hold custom event info
public class CustomEventArgs : EventArgs
{
public CustomEventArgs(string s)
{
message = s;
}
private string message;
public string Message
{
get { return message; }
set { message = value; }
}
}
// Class that publishes an event
class Publisher
{
// Declare the event using EventHandler<T>
public event EventHandler<CustomEventArgs> RaiseCustomEvent;
public void DoSomething()
{
// Write some code that does something useful here
// then raise the event. You can also raise an event
// before you execute a block of code.
OnRaiseCustomEvent(new CustomEventArgs("Did something"));
}
// Wrap event invocations inside a protected virtual method
// to allow derived classes to override the event invocation behavior
protected virtual void OnRaiseCustomEvent(CustomEventArgs e)
{
// Make a temporary copy of the event to avoid possibility of
// a race condition if the last subscriber unsubscribes
// immediately after the null check and before the event is raised.
EventHandler<CustomEventArgs> handler = RaiseCustomEvent;
// Event will be null if there are no subscribers
if (handler != null)
{
// Format the string to send inside the CustomEventArgs parameter
e.Message += String.Format(" at {0}", DateTime.Now.ToString());
// Use the () operator to raise the event.
handler(this, e);
}
}
}
//Class that subscribes to an event
class Subscriber
{
private string id;
public Subscriber(string ID, Publisher pub)
{
id = ID;
// Subscribe to the event using C# 2.0 syntax
pub.RaiseCustomEvent += HandleCustomEvent;
}
// Define what actions to take when the event is raised.
void HandleCustomEvent(object sender, CustomEventArgs e)
{
Console.WriteLine(id + " received this message: {0}", e.Message);
}
}
class Program
{
static void Main(string[] args)
{
Publisher pub = new Publisher();
Subscriber sub1 = new Subscriber("sub1", pub);
Subscriber sub2 = new Subscriber("sub2", pub);
// Call the method that raises the event.
pub.DoSomething();
// Keep the console window open
Console.WriteLine("Press Enter to close this window.");
Console.ReadLine();
}
}
}
An event is to a delegate type what a property is to any other type.
If you have a property such as this:
public string Name {get;set;}
Obviously you can do something like this:
string name = Name;
The property has an underlying string value that is accessed/modified by the property.
Similarly, an event has a delegate under the hood, and that delegate is of the type defined in the event declaration. That it's an event simply defines how handlers of that event are added/removed from that underlying delegate.
From within the declaring type (that's a key point; you can't do this externally) you can access that underlying delegate in order to invoke it. That's the reason for doing the code that you see; they're accessing the underlying delegate so that they can verify that it has some handlers within it, and if so, it invokes them.
So, to answer the questions explicitly:
Is not RaiseCustomEvent an event based on the definition at the top of the program?
RaiseCustomEvent is the type of the underlying delegate that the event wraps.
Why is an Event is equated to an EventHandler? These are two different types.
It's not strictly equality. It's pulling out the underlying delegate from within the event.
Where is RaiseCustomEvent being initialized? If it is not initialized how can we copy it or why do we want to copy something uninitialized to something else?
In this case it's using the automatic add/remove implementations that the framework will provide, rather than manually defining them. The automatically defined add handler will initialize the underlying delegate if it is currently null. If the event declaration would
define a custom add handler, it would need to handle that case.
What is the handler variable there for? Is that an event or an event handler?
It is a single delegate that represents the combination of all of the event handlers. Within it's definition will be an invocation list of all of the individual methods that make up that delegate. So it's not a single event handler, it's the collection of all of them. Since it's pulled out of the event it no longer strictly represents that event; it's a copy of what was in the event at some point in the past. You cannot change the event (i.e. add a new handler) using the delegate that you've pulled out of it.
I'll try the four questions you pose:
1) Is not RaiseCustomEvent an event based on the definition at the top of the program ?
The CustomEventArgs class holds some data (arguments) for the event we want to declare. It is used as type parameter in the type EventHandler<CustomEventArgs>. This last type is a delegate type, which means it represents one or more methods of the same signature and return type. (Zero methods will be a null reference as the value of the delegate.)
The type of the event RaiseCustomEvent is that delegate type, EventHandler<CustomEventArgs>.
2) Why an Event is equated to an EventHandler? These are two diffeent types
An event consists on a pair of special methods, accessors, one add accessor and one remove accessor. Both have one parameter of the same type which is called the type of the event. That type must be a delegate type. In this case that type is EventHandler<CustomEventArgs>.
In this example the event is a so-called field-like event. It generates a backing field of the same type, EventHandler<CustomEventArgs>, the delegate type. That field has the same name as the event itself!
When they do:
// Make a temporary copy of the event to avoid possibility of
// a race condition if the last subscriber unsubscribes
// immediately after the null check and before the event is raised.
EventHandler<CustomEventArgs> handler = RaiseCustomEvent;
they copy the current value of that backing field into the local variable handler. The comment describes why. They want to check for null before they invoke the delegate.
3) Where is RaiseCustomEvent have been initialized? If not initialized how can we copy or why do we want to copy something uninitialized to soemthig else.
It usually gets initialized when people use the add accessor of the event. This takes place through the special C# syntax +=. This is called subscribing to the event. See the Subscriber class.
Actually pub.RaiseCustomEvent += HandleCustomEvent; translates to pub.add_RaiseCustomEvent(HandleCustomEvent);, so it is a call to the add accessor. The add accessor is generated by the compiler (in a field-like event), and it initializes the backing field.
4)I do not know what is handler variable? is that an event or an event handler ?
It is a delegate. It is not an event. It is a copy of the backing field of the field-like event at one moment in time.
What I want to do is basically remove a function from an event, without knowing the function's name.
I have a FileSystemWatcher. If a file is created/renamed it checks its name. If it matches, it then moves it to a specific location. However, if the file is locked, it makes a lambda that attaches to a timer's tick event, waiting until the file is not locked. When it isn't, it moves the file and then removes itself from the event handler. I've seen lots of ways to do this, like keeping the instance, or making a named method. I can't do either of those here. What are my options?
There is no simple method to achieve this.
Preferred approach:
I don't see why you can't save the delegate. You don't have to save the instance as some field. It can be a local variable that is captured by your anonymous event handler:
EventHandler<TypeOfEventArgs> handler = null;
handler = (s, e) =>
{
// Do whatever you need to do here
// Remove event:
foo.Event -= handler;
}
foo.Event += handler;
I can't think of a single scenario where you can't use this.
Alternative approach without saving the delegate:
However, if you have such a scenario, it get's quite tricky.
You need to find the delegate that has been added as a handler to the event. Because you didn't save it, it is pretty hard to obtain it. There is no this to get a delegate of the currently executing method.
You can't use GetInvocationList() on the event either, because accessing an event outside the class it is defined in is restricted to adding and removing handlers, i.e. += and -=.
Creating a new delegate isn't possible either. While you can get access to the MethodInfo object defining your anonymous method, you can't get access to the instance of the class that method is declared in. This class is generated automatically by the compiler and calling this inside the anonymous method will return the instance of the class your normal method is defined in.
The only way I found that works is to find the field - if any - that the event uses and call GetInvocationList() on it. The following code demonstrates this with a dummy class:
void Main()
{
var foo = new Foo();
foo.Bar += (s, e) => {
Console.WriteLine("Executed");
var self = new StackFrame().GetMethod();
var eventField = foo.GetType()
.GetField("Bar", BindingFlags.NonPublic |
BindingFlags.Instance);
if(eventField == null)
return;
var eventValue = eventField.GetValue(foo) as EventHandler;
if(eventValue == null)
return;
var eventHandler = eventValue.GetInvocationList()
.OfType<EventHandler>()
.FirstOrDefault(x => x.Method == self)
as EventHandler;
if(eventHandler != null)
foo.Bar -= eventHandler;
};
foo.RaiseBar();
foo.RaiseBar();
}
public class Foo
{
public event EventHandler Bar;
public void RaiseBar()
{
var handler = Bar;
if(handler != null)
handler(this, EventArgs.Empty);
}
}
Please note that the string "Bar" that is passed to GetField needs to be the exact name of the field that is used by the event. This results in two problems:
The field can be named differently, e.g. when using an explicit event implementation. You need to manually find out the field name.
There might be no field at all. This happens if the event uses an explicit event implementation and just delegates to another event or stores the delegates in some other way.
Conclusion:
The alternative approach relies on implementation details, so don't use it if you can avoid it.
Steps to remove event handler with lambda expression:
public partial class Form1 : Form
{
private dynamic myEventHandler;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
myEventHandler = new System.EventHandler((sender2, e2) => this.button1_Click(sender, e, "Hi there"));
this.button1.Click += myEventHandler;
}
private void button1_Click(object sender, EventArgs e, string additionalInfo)
{
MessageBox.Show(additionalInfo);
button1.Click -= myEventHandler;
}
}
C# is still not OO enough? Here I'm giving a (maybe bad) example.
public class Program
{
public event EventHandler OnStart;
public static EventHandler LogOnStart = (s, e) => Console.WriteLine("starts");
public class MyCSharpProgram
{
public string Name { get; set; }
public event EventHandler OnStart;
public void Start()
{
OnStart(this, EventArgs.Empty);
}
}
static void Main(string[] args)
{
MyCSharpProgram cs = new MyCSharpProgram { Name = "C# test" };
cs.OnStart += LogOnStart; //can compile
//RegisterLogger(cs.OnStart); // Line of trouble
cs.Start(); // it prints "start" (of course it will :D)
Program p = new Program();
RegisterLogger(p.OnStart); //can compile
p.OnStart(p, EventArgs.Empty); //can compile, but NullReference at runtime
Console.Read();
}
static void RegisterLogger(EventHandler ev)
{
ev += LogOnStart;
}
}
RegisterLogger(cs.OnStart) leads to compile error, because "The event XXX can only appear on the left hand side of += or -= blabla". But why RegisterLogger(p.OnStart) can? Meanwhile, although I registered p.OnStart, it will also throw an NullReferenceException, seems that p.OnStart is not "really" passed to a method.
"The event XXX can only appear on the left hand side of += or -= blabla"
This is actually because C# is "OO enough." One of the core principles of OOP is encapsulation; events provide a form of this, just like properties: inside the declaring class they may be accessed directly, but outside they are only exposed to the += and -= operators. This is so that the declaring class is in complete control of when the events are called. Client code can only have a say in what happens when they are called.
The reason your code RegisterLogger(p.OnStart) compiles is that it is declared from within the scope of the Program class, where the Program.OnStart event is declared.
The reason your code RegisterLogger(cs.OnStart) does not compile is that it is declared from within the scope of the Program class, but the MyCSharpProgram.OnStart event is declared (obviously) within the MyCSharpProgram class.
As Chris Taylor points out, the reason you get a NullReferenceException on the line p.OnStart(p, EventArgs.Empty); is that calling RegisterLogger as you have it assigns a new value to a local variable, having no affect on the object to which that local variable was assigned when it was passed in as a parameter. To understand this better, consider the following code:
static void IncrementValue(int value)
{
value += 1;
}
int i = 0;
IncrementValue(i);
// Prints '0', because IncrementValue had no effect on i --
// a new value was assigned to the COPY of i that was passed in
Console.WriteLine(i);
Just as a method that takes an int as a parameter and assigns a new value to it only affects the local variable copied to its stack, a method that takes an EventHandler as a parameter and assigns a new value to it only affects its local variable as well (in an assignment).
Make the following change to RegisterLogger, declare ev as a reference argument to the event handler.
static void RegisterLogger(ref EventHandler ev)
{
ev += LogOnStart;
}
Then your call point will also need to use the 'ref' keyword when invoking the method as follows
RegisterLogger(ref p.OnStart);
The reason this fails to compile:
RegisterLogger(cs.OnStart);
... is that the event handler and the method you are passing it to are in different classes. C# treats events very strictly, and only allows the class that the event appears in to do anything other than add a handler (including pass it to functions, or invoke it).
For example, this won't compile either (because it is in a different class):
cs.OnStart(cs, EventArgs.Empty);
As for not being able to pass an event handler to a function this way, I'm not sure. I am guessing events operate like value types. Passing it by ref will fix your problem, though:
static void RegisterLogger(ref EventHandler ev)
{
ev += LogOnStart;
}
When an object declares an event, it only exposes methods to add and/or remove handlers to the event outside of the class (provided it doesn't redefine the add/remove operations). Within it, it is treated much like an "object" and works more or less like a declared delegate variable. If no handlers are added to the event, it's as if it were never initialized and is null. It is this way by design. Here is the typical pattern used in the framework:
public class MyCSharpProgram
{
// ...
// define the event
public event EventHandler SomeEvent;
// add a mechanism to "raise" the event
protected virtual void OnSomeEvent()
{
// SomeEvent is a "variable" to a EventHandler
if (SomeEvent != null)
SomeEvent(this, EventArgs.Empty);
}
}
// etc...
Now if you must insist on exposing the delegate outside of your class, just don't define it as an event. You could then treat it as any other field or property.
I've modified your sample code to illustrate:
public class Program
{
public EventHandler OnStart;
public static EventHandler LogOnStart = (s, e) => Console.WriteLine("starts");
public class MyCSharpProgram
{
public string Name { get; set; }
// just a field to an EventHandler
public EventHandler OnStart = (s, e) => { /* do nothing */ }; // needs to be initialized to use "+=", "-=" or suppress null-checks
public void Start()
{
// always check if non-null
if (OnStart != null)
OnStart(this, EventArgs.Empty);
}
}
static void Main(string[] args)
{
MyCSharpProgram cs = new MyCSharpProgram { Name = "C# test" };
cs.OnStart += LogOnStart; //can compile
RegisterLogger(cs.OnStart); // should work now
cs.Start(); // it prints "start" (of course it will :D)
Program p = new Program();
RegisterLogger(p.OnStart); //can compile
p.OnStart(p, EventArgs.Empty); //can compile, but NullReference at runtime
Console.Read();
}
static void RegisterLogger(EventHandler ev)
{
// Program.OnStart not initialized so ev is null
if (ev != null) //null-check just in case
ev += LogOnStart;
}
}
This question already has answers here:
How to ensure an event is only subscribed to once
(8 answers)
Closed 8 years ago.
Duplicate of: How to ensure an event is only subscribed to once
and Has an event handler already been added?
I have a singleton that provides some service and my classes hook into some events on it, sometimes a class is hooking twice to the event and then gets called twice.
I'm looking for a classical way to prevent this from happening. somehow I need to check if I've already hooked to this event...
How about just removing the event first with -= , if it is not found an exception is not thrown
/// -= Removes the event if it has been already added, this prevents multiple firing of the event
((System.Windows.Forms.WebBrowser)sender).Document.Click -= new System.Windows.Forms.HtmlElementEventHandler(testii);
((System.Windows.Forms.WebBrowser)sender).Document.Click += new System.Windows.Forms.HtmlElementEventHandler(testii);
Explicitly implement the event and check the invocation list. You'll also need to check for null:
using System.Linq; // Required for the .Contains call below:
...
private EventHandler foo;
public event EventHandler Foo
{
add
{
if (foo == null || !foo.GetInvocationList().Contains(value))
{
foo += value;
}
}
remove
{
foo -= value;
}
}
Using the code above, if a caller subscribes to the event multiple times, it will simply be ignored.
I've tested each solution and the best one (considering performance) is:
private EventHandler _foo;
public event EventHandler Foo {
add {
_foo -= value;
_foo += value;
}
remove {
_foo -= value;
}
}
No Linq using required. No need to check for null before cancelling a subscription (see MS EventHandler for details). No need to remember to do the unsubscription everywhere.
You really should handle this at the sink level and not the source level. That is, don't prescribe event handler logic at the event source - leave that to the handlers (the sinks) themselves.
As the developer of a service, who are you to say that sinks can only register once? What if they want to register twice for some reason? And if you are trying to correct bugs in the sinks by modifying the source, it's again a good reason for correcting these issues at the sink-level.
I'm sure you have your reasons; an event source for which duplicate sinks are illegal is not unfathomable. But perhaps you should consider an alternate architecture that leaves the semantics of an event intact.
You need to implement the add and remove accessors on the event, and then check the target list of the delegate, or store the targets in a list.
In the add method, you can use the Delegate.GetInvocationList method to obtain a list of the targets already added to the delegate.
Since delegates are defined to compare equal if they're linked to the same method on the same target object, you could probably run through that list and compare, and if you find none that compares equal, you add the new one.
Here's sample code, compile as console application:
using System;
using System.Linq;
namespace DemoApp
{
public class TestClass
{
private EventHandler _Test;
public event EventHandler Test
{
add
{
if (_Test == null || !_Test.GetInvocationList().Contains(value))
_Test += value;
}
remove
{
_Test -= value;
}
}
public void OnTest()
{
if (_Test != null)
_Test(this, EventArgs.Empty);
}
}
class Program
{
static void Main()
{
TestClass tc = new TestClass();
tc.Test += tc_Test;
tc.Test += tc_Test;
tc.OnTest();
Console.In.ReadLine();
}
static void tc_Test(object sender, EventArgs e)
{
Console.Out.WriteLine("tc_Test called");
}
}
}
Output:
tc_Test called
(ie. only once)
Microsoft's Reactive Extensions (Rx) framework can also be used to do "subscribe only once".
Given a mouse event foo.Clicked, here's how to subscribe and receive only a single invocation:
Observable.FromEvent<MouseEventArgs>(foo, nameof(foo.Clicked))
.Take(1)
.Subscribe(MyHandler);
...
private void MyHandler(IEvent<MouseEventArgs> eventInfo)
{
// This will be called just once!
var sender = eventInfo.Sender;
var args = eventInfo.EventArgs;
}
In addition to providing "subscribe once" functionality, the RX approach offers the ability to compose events together or filter events. It's quite nifty.
Create an Action instead of an event. Your class may look like:
public class MyClass
{
// sender arguments <----- Use this action instead of an event
public Action<object, EventArgs> OnSomeEventOccured;
public void SomeMethod()
{
if(OnSomeEventOccured!=null)
OnSomeEventOccured(this, null);
}
}
have your singleton object check it's list of who it notifies and only call once if duplicated. Alternatively if possible reject event attachment request.
In silverlight you need to say e.Handled = true; in the event code.
void image_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
e.Handled = true; //this fixes the double event fire problem.
string name = (e.OriginalSource as Image).Tag.ToString();
DoSomething(name);
}
Please tick me if this helps.