Unsubscribing from anonymous event handler inside an static method (extension method) - c#

I have an extension method to subscribe a PropertyChanged event of an object that implements INotifyPropertyChanged.
I would like that the event fires just once. Not more.
This is my method.
public static void OnPropertyChanged<T>(this INotifyPropertyChanged target, string propertyName, Action action)
{
if (target == null)
{
return;
}
PropertyChangedEventHandler handler = (obj, e) =>
{
if (propertyName == e.PropertyName)
{
action();
}
};
target.PropertyChanged -= handler;
target.PropertyChanged += handler;
}
But it does not work. I cannnot remove the event handler so the event fires every time I call this method.
I have try a different approach. Instead of using annonymous methods, something more traditional, like this:
public static void OnPropertyChanged<T>(this INotifyPropertyChanged target, string propertyName, Action action)
{
if (target == null)
{
return;
}
target.PropertyChanged -= target_PropertyChanged;
target.PropertyChanged += target_PropertyChanged;
}
static void target_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
//do stuff here
}
And it just works fine. The event fires just once, but I also need the Action parameter. I cannot use it with this approach.
Any workaround or different aproach to solve this issue?Is there something strange with anonymous methods inside static methods?
Thanks in advance.

That is a limitation of using anonymous methods as event handlers. They cannot be removed as you would a normal method (which is technically a delegate instance automatically create via a method group conversion) because anonymous methods get compiled into a compiler-generated container class and a new instance of the class is created each time.
In order to preserve the action parameter you could create a container class which would have the delegate for your event handler inside. The class can be declared private inside the of the other class you're working with - or made internal, maybe in a "Helpers" namespace. It would look something like this:
class DelegateContainer
{
public DelegateContainer(Action theAction, string propName)
{
TheAction = theAction;
PopertyName = propName;
}
public Action TheAction { get; private set; }
public string PropertyName { get; private set; }
public void PropertyChangedHandler(object sender, PropertyChangedEventArgs e)
{
if(PropertyName == e.PropertyName)
TheAction();
}
}
Then, create and store the reference to the container in your class. You might create a static member currentContainer and then set the handler like this:
private static DelegateContainer currentContainer;
public static void OnPropertyChanged<T>(this INotifyPropertyChanged target, string propertyName, Action action)
{
if (target == null)
{
return;
}
if(currentContainer != null)
target.PropertyChanged -= currentContainer.PropertyChangedHandler;
currentContainer = new DelegateContainer(action, propertyName);
target.PropertyChanged += currentContainer.PropertyChangedHandler;
}

You can get your first example to work if you unsubscribe from within the event handler itself.
public static void OnPropertyChanged<T>(this INotifyPropertyChanged target, string propertyName, Action action)
{
if (target == null)
{
return;
}
// Declare the handler first, in order to create
// a concrete reference that you can use from within
// the delegate
PropertyChangedEventHandler handler = null;
handler = (obj, e) =>
{
if (propertyName == e.PropertyName)
{
obj.PropertyChanged -= handler; //un-register yourself
action();
}
};
target.PropertyChanged += handler;
}
The above code serves as a "one and done" event handler. You can register an unlimited number of these, and each one will only be executed once before unregistering itself.
Keep in mind that it's possible to have one of these handlers execute multiple times, if you raise the event across multiple threads in short succession. To prevent this, you might need to create a static Dictionary(T,T) mapping object instances to "lock objects," and add some sentry code to ensure that a handler is only executed once. Those implementation specifics seem to be a bit outside the scope of your question as currently written, however.

Technically, it's not the same anonymous method you are trying to unsubscribe. .NET creates new instance of that method every time your OnPropertyChanged called. That's why unsubscription will not work.

Related

C# CS0079 Event Handling Compile Errors

I can't compile the following code.
Compile error CS0079 : The event 'CustomEvent' can only appear on the left hand side of += or -=
if (CustomEvent != null) //CS0079
CustomEvent(null, null); //CS0079
How can I make this work?
My implementation is like this:
public delegate void EventHandler(object sender, EventArgs e);
public static event EventHandler CustomEvent
{
add { CustomEvent += value; }
remove { CustomEvent -= value; }
}
private static void Func()
{
if (CustomEvent != null) //CS0079
CustomEvent(null, null); //CS0079
}
Your edit shows a recursive call: you are declaring a custom event, which means you are meant to provide a backing field; for example:
private static EventHandler customEvent;
public static event EventHandler CustomEvent
{
add { customEvent += value; }
remove { customEvent -= value; }
}
private static void Func()
{
var tmp = customEvent;
if (tmp != null) tmp(null, null);
}
Note that in Func I am referring to the field (customEvent), not the event (CustomEvent).
However, this is simpler are better (thread-safe) as a field-like event:
public static event EventHandler CustomEvent;
private static void Func()
{
var tmp = CustomEvent;
if (tmp != null) tmp(null, null);
}
A field-like event uses the event keyword, but omits the accessors: the compiler adds a lot of boilerplate for you (a backing field, and thread-safe add/remove implementations). Further, it allows access to the backing filed via the event name (from the declaring type), hence how the line var tmp = CustomEvent; works.
Also: be very careful with static events; they are a great way to accidentally keep lots of objects alive.
You can only test/invoke an event if it is a field-like event declared in the current type. So: there are two scenarios that would cause this:
it isn't a field-like event, but has custom add/remove accessors: in which case, only your custom code knows how the delegate is stored
it isn't declared in the current type, but is in a base-type or some unrelated object: in which case, you'll need to get the declaring type to invoke the event, usually via an OnCustomEvent method. In the case of a base-type, the convention would be to make this method protected virtual, which allows sub-classes to invoke the event and hook into the event via override
(comments)
It looks like the case1. however, I don't understand what to do to resolve this issue.
If you have custom add/remove, then how to invoke it is implementation-specific (I could tell you more if I could see the add/remove), but let's look at two common implementations:
1a: a backing delegate:
private EventHandler someEvent;
public event EventHandler SomeEvent
{
add { someEvent += value; }
remove { someEvent -= value; }
}
in this case, the "invoke" implementation would be simply:
if(someEvent != null) someEvent(this, EventArgs.Empty);
or if you are feeling extra-cautious:
var handler = someEvent;
if(handler != null) handler(this, EventArgs.Empty);
1b: an EventHandlerList (used for sparse events):
private static readonly object SomeEventKey = new object();
public event EventHandler SomeEvent
{
add { Events.AddHandler(SomeEventKey, value); }
remove { Events.RemoveHandler(SomeEventKey, value); }
}
in which case the invoke implementation would be:
var handler = (EventHandler)Events[SomeEventKey];
if(handler != null) handler(this, EventArgs.Empty);

how to create a generic event raising method

I have a set of events that have the same signature . now I wonder if I can create a generic event handler raising method to do this for all of the events ?
is this possible to send an event as <T> ?
If this is all within a single class, you can make a method to raise the event which works with any of them. For example, if your events all were EventHandler<T>, you could use:
private void RaiseEvent<T>(EventHandler<T> eventHandler, T eventArgs)
{
if (eventHandler != null)
{
eventHandler(this, eventArgs);
}
}
You could then call this via:
this.RaiseEvent(this.MyEvent, new MyEventArgs("Foo"));
For a static version of Reed Copsey's reply, I created a static class Event:
public static class Event
{
public static bool Raise<T>(Object source, EventHandler<T> eventHandler, T eventArgs) where T : EventArgs
{
EventHandler<T> handler = eventHandler;
if (handler != null)
{
handler(source, eventArgs);
return true;
}
return false;
}
}
This also assumes your event handlers are of the type EventHandler<T>. The return type was changed from void to bool and returns whether there were any listeners of the event. Rarely used, so feel free to change back to void.
Example usage:
public event EventHandler<FooArgs> FooHappend;
public void Foo()
{
Event.Raise(this, FooHappend, new FooArgs("Hello World!");
}
See this. It describes what you want.
You can create a typed event by using a typed delegate and using that for your event:
public delegate void myDel<T>(T stuff);
public event myDel<int> myEvent;
public doStuff()
{
myDel(1);
}

Prevent next event handler being called

I have two event handlers wired up to a button click in a Windows form like so:
this.BtnCreate.Click += new System.EventHandler(new RdlcCreator().FirstHandler);
this.BtnCreate.Click += new System.EventHandler(this.BtnCreate_Click);
both are being called correctly.
However is it possible within FirstHandler() to prevent BtnCreate_Click() being executed? Something like:
void FirstHandler(object sender, EventArgs e)
{
if (ConditionSatisfied)
//Prevent next handler in sequence being executed
}
I know I could just unsubscribe the event, but can this be done programmatically (from within the method)?
As far as I know there is no solution for this. That's because there is no guarantee for the order in which the event handlers are called when the event happens.
Because of that you are not supposed to rely on their order in any way.
Why don't you just replace them with one eventhandler? Something like this:
var rdlc = new RdlcCreator();
this.BtnCreate.Click += (sender, e) => {
rdlc.FirstHandler(sender, e);
if (!rdlc.HasHandledStuff) { // <-- You would need some kind of flag
this.BtnCreate_Click(sender, e);
}
};
That way you can also guarantee the order of the handlers. Alternatively, use the above implementation, but change the signature of FirstHandler to return a bool indicating the condition (as in this case it doesn't really need to have the event's signature anymore):
if (!rdlc.FirstHandler(sender, e)) {
this.BtnCreate_Click(sender, e);
}
EDIT: OR, you just pass the second handler to FirstHandler.
Change the signature of FirstHandler to this:
void FirstHandler(object sender, EventArgs e, EventHandler nextHandler) {
if (ConditionSatisfied) {
// do stuff
}
else if (nextHandler != null) {
nextHandler(sender, e);
}
}
and then:
this.BtnCreate.Click +=
(s, e) => new RdlcCreator().Firsthandler(s, e, this.BtnCreate_Click);
System.ComponentModel namespace contains a CancelEventHandler delegate which is used for this purpose. One of the arguments it provides is a CancelEventArgs instance which contains a boolean Cancel property which can be set be any of the handlers to signal that execution of the invocation list should be stopped.
However, to attach it to a plain EventHandler delegate, you will need to create your own wrapper, something like:
public static class CancellableEventChain
{
public static EventHandler CreateFrom(params CancelEventHandler[] chain)
{
return (sender, dummy) =>
{
var args = new CancelEventArgs(false);
foreach (var handler in chain)
{
handler(sender, args);
if (args.Cancel)
break;
}
};
}
}
For your example, you would use it like this:
this.BtnCreate.Click += CancellableEventChain.CreateFrom(
new RdlcCreator().FirstHandler,
this.BtnCreate_Click
/* ... */
);
Of course, you would need to capture the created chain handler in a field if you need to unsubscribe (detach) it later.
Add the following condition in this.BtnCreate_Click which is the the second event
BtnCreate_Click(object sender, EventArgs e)
{
if (!ConditionSatisfied) //Prevent next handler in sequence being executed
{
// your implementation goes here
}
}
I suggest you to create a some kind of class wrapper. So, you could store there some kind of event flag group (16bit integer, for example) and a few methods to set or unset individual bits (where each means to invoke or not particular EventHandler). You can easily store any count of the Eventhandlers or even Actions, in the class, and invoke in any order you want.
Was finding the solution to the same question, but no luck. So had to resolve myself.
A base class for Cancelable event args
public class CancelableEventArgs
{
public bool Cancelled { get; set; }
public void CancelFutherProcessing()
{
Cancelled = true;
}
}
Next defines the extension method for the EventHandler, note that Invocation List subscribers invoked in backward order (in my case UI elements subscibe the event as they added to components, so which element is rendered later has most visiblility and more priority)
public static class CommonExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SafeInvokeWithCancel<T>(this EventHandler<T> handler, object sender, T args) where T : CancelableEventArgs
{
if (handler != null)
{
foreach (var d in handler.GetInvocationList().Reverse())
{
d.DynamicInvoke(sender, args);
if (args.Cancelled)
{
break;
}
}
}
}
And here is the usage
public class ChessboardEventArgs : CancelableEventArgs
{
public Vector2 Position { get; set; }
}
So if an UI element has some behaviour on the event, it cancells futher processing
game.OnMouseLeftButtonDown += (sender, a) =>
{
var xy = GetChessboardPositionByScreenPosition(a.XY);
if (IsInside(xy))
{
var args = new ChessboardEventArgs { Position = xy };
OnMouseDown.SafeInvokeWithCancel(this, args);
a.CancelFutherProcessing();
}
};

How to use a existed event in C#?

I am working a problem which is about delegate and event. I am a newbid in this aspect. I don't know how to call the event.
Would some tell me?
Thanks in advance.
Here is simple example to call event....
// event_keyword.cs
using System;
public delegate void MyDelegate(); // delegate declaration
public interface I
{
event MyDelegate MyEvent;
void FireAway();
}
public class MyClass: I
{
public event MyDelegate MyEvent;
public void FireAway()
{
if (MyEvent != null)
MyEvent();
}
}
public class MainClass
{
static private void f()
{
Console.WriteLine("This is called when the event fires.");
}
static public void Main ()
{
I i = new MyClass();
i.MyEvent += new MyDelegate(f);
i.FireAway();
}
}
There is Link which may helpful.
The event can be invoked in the class in which it is declared. First you'll usually want to check if your event is null.
if (MyEvent != null) MyEvent(this, new EventArgs());
The arguments you pass to the event will depend on the declaration of the event. To give you a little more background, an event is just a compiler trick. When an event such as
public event ChangedEventHandler Changed;
is compiled it will look like
protected ChangedEventHandler _change;
public ChangedEventHandler Change
{
add { _change += value; }
remove { _change -= value; }
}
so anything inside where it is declared will use _change, while anything outside will use Change. In other words, inside where it is declared, it is just a delegate, and all the normal rules apply.
To resuse the event you just need to attach event with the you control for example .
buttonone.Click+= event1;
buttonTwo.Click+= event1;
Fore more details have look : C# Event Implementation Fundamentals, Best Practices and Conventions
Once you have defined the delegate, you need to define when to call the event. I mean you can call the event at assignment of any value to the specific variable.
here is the example of defining the delegate with the same variable class.
public class callbackdel : EventArgs
{
public readonly string resp = null;
public callbackdel(string s)
{
resp = s;
}
}
public delegate void WorkerEndHandler(object o, callbackdel e);
Now in the control you are using, you need to add this method.
public void OnWorkEnd(object o, callbackdel e)
{
WorkEnd(o, e);
}
after creating method and defining the delegate, you can fire the event from any of the delegate simply by calling the method.
OnWorkEnd((object)this, e);
When using an Event you first have to declare it:
// Create some custom arguments for the event
public class SampleEventArgs
{
public SampleEventArgs(string s)
{
Text = s;
}
public String Text {get; private set;}
}
// Define a class that uses the event
public class EventPublisher
{
// Declare the delegate
public delegate void SampleEventHandler(object sender, SampleEventArgs e);
// Declare the event.
public event SampleEventHandler SampleEvent;
// Wrap the event in a protected virtual method
// to enable derived classes to raise the event.
protected virtual void RaiseSampleEvent()
{
// Raise the event by using the () operator.
if (SampleEvent != null)
SampleEvent(this, new SampleEventArgs("Hello"));
}
}
You can then subscribe to the event:
EventPublisher publisher = new EventPublisher();
publisher.SampleEvent += new EventPublisher.SampleEventHandler(SampleEventHandler);
public void SampleEventHandler(object sender, SampleEventArgs args)
{
}
Your event handler will be called when EventPublisher executes RaiseSampleEvent()

array of events in C#?

basically:
public delegate void RecvCommandHandler (ChatApplication sender, byte[] content);
event RecvCommandHandler[] commands = new RecvCommandHandler[255];
I want to activate a different method/function for each command number, but I am really uncertain of the syntax. How am I supposed to do it?
I think I'll go with just an array of delegates for this one, but the question is still interesting.
You could create an array of a class with operator overloading to simulate the behavior you are interested in...
public delegate void EventDelegate(EventData kEvent);
public class EventElement
{
protected event EventDelegate eventdelegate;
public void Dispatch(EventData kEvent)
{
if (eventdelegate != null)
{
eventdelegate(kEvent);
}
}
public static EventElement operator +(EventElement kElement, EventDelegate kDelegate)
{
kElement.eventdelegate += kDelegate;
return kElement;
}
public static EventElement operator -(EventElement kElement, EventDelegate kDelegate)
{
kElement.eventdelegate -= kDelegate;
return kElement;
}
}
public EventElement[] commands = new EventElement[255];
commands[100] += OnWhatever;
commands[100].Dispatch(new EventData());
commands[100] -= OnWhatever;
There's really no concept of an array of events - it's like talking about an array of properties. Events are really just methods which let you subscribe and unsubscribe handlers. If you need to be able to do this by index, I suggest you just have a pair of methods. (AddCommandHandler(int, RecvCommandHandler) and RemoveCommandHandler(int, RecvCommandHandler)). That won't support the normal event handling syntactic sugar, of course, but I don't see that there's a lot of alternative.
The other option is to specify and index in the delegate prototype and have one event handler that "delegates" to the others, e.g.:
public delegate void RecvCommandHandler (int id, ChatApplication sender, byte[] content);
// ...
private RecvCommandHandler[] internalhandlers;
public void MyCommandHandler(int id, ChatApplication sender, byte[] content)
{
internalHandlers[id](id, sender, content);
}
I was just looking for the same answer, however my class is also event sender for WPF, so it should look as much as normal C#/WPF event sender class. So I simply added this:
To sender:
enum with properties name -- this is lame workaround for lack of nameof
one additional method to record requests
To receiver:
request event for given enum
The code, sender:
public enum Properties
{
NetworkFileName,
DatasetFileName,
LearningWatch
}
private string network_filename;
public string NetworkFileName
{
get { return network_filename; }
private set
{
if (network_filename != value)
{
network_filename = value;
OnPropertyChanged(Properties.NetworkFileName.ToString());
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
public void OnChange(Properties prop, Action<object, PropertyChangedEventArgs> action)
{
PropertyChanged += new PropertyChangedEventHandler((obj, args) => { if (args.PropertyName == prop.ToString()) action(obj, args); });
}
And to the receiver:
private void OnNetworkLoaded(object sender, PropertyChangedEventArgs e)
{
SetTitle();
}
...
ExpManager.OnChange(ExperimentManager.Properties.DatasetFileName, OnDatasetLoaded);
It is still ugly, but at least:
I don't have to deal with "ifs" in receiver
I can easily create multiple event handlers
it is compatible with WPF
no magic strings (I hate those)
Disadvantage:
obsfuscation ruins this (but I have special class for that case, this project is just for me, so no problem here)

Categories