I am currently trying to write an extension function to be able to easily attach an action that is only used once when the event is fired, then unsubscribed.
I am trying something like this:
public static void AttachOnce<TEventArgs>([NotNull] this EventHandler<TEventArgs> me, [NotNull] Action<object, TEventArgs> action)
where TEventArgs : System.EventArgs
{
var handler = me;
EventHandler<TEventArgs> wrappedAction = null;
wrappedAction = (sender, args) =>
{
action(sender, args);
handler -= wrappedAction;
};
handler += wrappedAction;
}
But ReSharper complains on the unsubscribe that handler is "Access to modified closure".
I know what this means, so I made the local variable for the closure already, but it doesn't seem to resolve it. What is failing here?
The direct hard-coded code works. Something like this:
var file = new FileSystemWatcher("path/to/file");
FileSystemEventHandler handler = null;
handler = (sender, args) =>
{
// My action code here
file.Changed -= handler;
};
file.Changed += handler;
EDIT 1 (2018-10-09 11:43 CET):
I may just have been too fast, asking a question before thoroughly thinking it through.
You can't create extension methods on Events. At all. It's just not possible in C#. So I can't even test why ReSharper is complaining and if it is right, because a call like file.Changed.AttachOnce(action) is not valid. It says "The event 'Changed' can only appear on the left hand side of += or -=".
I have found some more sources for similar requests/questions:
http://www.hardkoded.com/blog/csharp-wishlist-extension-for-events
One time generic event call?
I've been thinking about a different but much simpler approach, using a "self-detaching" inline handler which would be used like this:
obj.Event += (s, e) =>
{
Detach(obj, nameof(obj.Event));
// ..do stuff..
};
The Detach method would look like this and could be put anywhere you like (most likely a static helper class):
public static void Detach(object obj, string eventName)
{
var caller = new StackTrace().GetFrame(1).GetMethod();
var type = obj.GetType();
foreach (var field in type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic))
{
if (typeof(Delegate).IsAssignableFrom(field.FieldType))
{
var handler = (field.GetValue(obj) as Delegate)?.GetInvocationList().FirstOrDefault(m => m.Method.Equals(caller));
if (handler != null)
{
type.GetEvent(eventName).RemoveEventHandler(obj, handler);
return;
}
}
}
}
So for your example the code would look like this:
file.Changed += (s, e) =>
{
Detach(file, nameof(file.Changed));
// My action code here
};
In this case, it's okay.
ReSharper simply warns that handler is different between the time you declare and the time it is executed.
Not sure how exactly you want to design your extension method, but maybe this will get you started:
public static void OnChangedOnce(this FileSystemWatcher instance, Action<object, FileSystemEventArgs> action)
{
var file = instance;
var wrappedAction = action;
FileSystemEventHandler handler = null;
handler = (object sender, FileSystemEventArgs args) =>
{
wrappedAction(sender, args);
file.Changed -= handler;
};
file.Changed += handler;
file.EnableRaisingEvents = true; // mandatory
}
var file = new FileSystemWatcher("path/to/file");
file.OnChangedOnce((sender, args)) =>
{
// your action here
});
Related
I want do something like this:
Button btn1 = new Button();
btn1.Click += new EventHandler(btn1_Click);
Button btn2 = new Button();
// Take whatever event got assigned to btn1 and assign it to btn2.
btn2.Click += btn1.Click; // The compiler says no...
Where btn1_Click is already defined in the class:
void btn1_Click(object sender, EventArgs e)
{
//
}
This won't compile, of course ("The event 'System.Windows.Forms.Control.Click' can only appear on the left hand side of += or -="). Is there a way to take the event handler from one control and assign it to another at runtime? If that's not possible, is duplicating the event handler and assigning it to another control at runtime doable?
A couple of points: I have googled the heck out of this one for awhile and found no way of doing it yet. Most of the attempted approaches involve reflection, so if you read my question and think the answer is incredibly obvious, please try to compile the code in Visual Studio first. Or if the answer really is incredibly obvious, please feel free to slap me with it. Thanks, I'm really looking forward to seeing if this is possible.
I know I could just do this:
btn2.Click += new EventHandler(btn1_Click);
That's not what I'm looking for here.
This is also not what I'm looking for:
EventHandler handy = new EventHandler(btn1_Click);
Button btn1 = new Button();
btn1.Click += handy;
Button btn2 = new Button();
btn2.Click += handy;
Yeah, it's technically possible. Reflection is required because many of the members are private and internal. Start a new Windows Forms project and add two buttons. Then:
using System;
using System.ComponentModel;
using System.Windows.Forms;
using System.Reflection;
namespace WindowsFormsApplication1 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
button1.Click += new EventHandler(button1_Click);
// Get secret click event key
FieldInfo eventClick = typeof(Control).GetField("EventClick", BindingFlags.NonPublic | BindingFlags.Static);
object secret = eventClick.GetValue(null);
// Retrieve the click event
PropertyInfo eventsProp = typeof(Component).GetProperty("Events", BindingFlags.NonPublic | BindingFlags.Instance);
EventHandlerList events = (EventHandlerList)eventsProp.GetValue(button1, null);
Delegate click = events[secret];
// Remove it from button1, add it to button2
events.RemoveHandler(secret, click);
events = (EventHandlerList)eventsProp.GetValue(button2, null);
events.AddHandler(secret, click);
}
void button1_Click(object sender, EventArgs e) {
MessageBox.Show("Yada");
}
}
}
If this convinces you that Microsoft tried really hard to prevent your from doing this, you understood the code.
No, you can't do this. The reason is encapsulation - events are just subscribe/unsubscribe, i.e. they don't let you "peek inside" to see what handlers are already subscribed.
What you could do is derive from Button, and create a public method which calls OnClick. Then you just need to make btn1 an instance of that class, and subscribe a handler to btn2 which calls btn1.RaiseClickEvent() or whatever you call the method.
I'm not sure I'd really recommend it though. What are you actually trying to do? What's the bigger picture?
EDIT: I see you've accepted the version which fetches the current set of events with reflection, but in case you're interested in the alternative which calls the OnXXX handler in the original control, I've got a sample here. I originally copied all events, but that leads to some very odd effects indeed. Note that this version means that if anyone subscribes to an event in the original button after calling CopyEvents, it's still "hooked up" - i.e. it doesn't really matter when you associate the two.
using System;
using System.Drawing;
using System.Reflection;
using System.Windows.Forms;
class Test
{
static void Main()
{
TextBox output = new TextBox
{
Multiline = true,
Height = 350,
Width = 200,
Location = new Point (5, 15)
};
Button original = new Button
{
Text = "Original",
Location = new Point (210, 15)
};
original.Click += Log(output, "Click!");
original.MouseEnter += Log(output, "MouseEnter");
original.MouseLeave += Log(output, "MouseLeave");
Button copyCat = new Button
{
Text = "CopyCat",
Location = new Point (210, 50)
};
CopyEvents(original, copyCat, "Click", "MouseEnter", "MouseLeave");
Form form = new Form
{
Width = 400,
Height = 420,
Controls = { output, original, copyCat }
};
Application.Run(form);
}
private static void CopyEvents(object source, object target, params string[] events)
{
Type sourceType = source.GetType();
Type targetType = target.GetType();
MethodInfo invoker = typeof(MethodAndSource).GetMethod("Invoke");
foreach (String eventName in events)
{
EventInfo sourceEvent = sourceType.GetEvent(eventName);
if (sourceEvent == null)
{
Console.WriteLine("Can't find {0}.{1}", sourceType.Name, eventName);
continue;
}
// Note: we currently assume that all events are compatible with
// EventHandler. This method could do with more error checks...
MethodInfo raiseMethod = sourceType.GetMethod("On"+sourceEvent.Name,
BindingFlags.Instance |
BindingFlags.Public |
BindingFlags.NonPublic);
if (raiseMethod == null)
{
Console.WriteLine("Can't find {0}.On{1}", sourceType.Name, sourceEvent.Name);
continue;
}
EventInfo targetEvent = targetType.GetEvent(sourceEvent.Name);
if (targetEvent == null)
{
Console.WriteLine("Can't find {0}.{1}", targetType.Name, sourceEvent.Name);
continue;
}
MethodAndSource methodAndSource = new MethodAndSource(raiseMethod, source);
Delegate handler = Delegate.CreateDelegate(sourceEvent.EventHandlerType,
methodAndSource,
invoker);
targetEvent.AddEventHandler(target, handler);
}
}
private static EventHandler Log(TextBox output, string text)
{
return (sender, args) => output.Text += text + "\r\n";
}
private class MethodAndSource
{
private readonly MethodInfo method;
private readonly object source;
internal MethodAndSource(MethodInfo method, object source)
{
this.method = method;
this.source = source;
}
public void Invoke(object sender, EventArgs args)
{
method.Invoke(source, new object[] { args });
}
}
}
I did some digging around with #nobugz's solution and came up with this generic version which could be used on most general-purpose objects.
What I found out is that events for, dare I say, automatic events actually are compiled with a backing delegate field of the same name:
So here's one for stealing event handlers for simpler objects:
class Program
{
static void Main(string[] args)
{
var d = new Dummy();
var d2 = new Dummy();
// Use anonymous methods without saving any references
d.MyEvents += (sender, e) => { Console.WriteLine("One!"); };
d.MyEvents += (sender, e) => { Console.WriteLine("Two!"); };
// Find the backing field and get its value
var theType = d.GetType();
var bindingFlags = BindingFlags.NonPublic | BindingFlags.Instance;
var backingField = theType.GetField("MyEvents", bindingFlags);
var backingDelegate = backingField.GetValue(d) as Delegate;
var handlers = backingDelegate.GetInvocationList();
// Bind the handlers to the second instance
foreach (var handler in handlers)
d2.MyEvents += handler as EventHandler;
// See if the handlers are fired
d2.DoRaiseEvent();
Console.ReadKey();
}
}
class Dummy
{
public event EventHandler MyEvents;
public void DoRaiseEvent() { MyEvents(this, new EventArgs()); }
}
Thought it might be useful to some.
But do note that the way events are wired in Windows Forms components is rather different. They are optimized so that multiple events doesn't take up a lot of memory just holding nulls. So it'll need a little more digging around, but #nobugz has already done that :-)
The article Delegates and events about combined delegates might help clarify a lot of points in answers.
You could use a common event handler for your buttons and your picture boxes (as per the comments on an earlier answer) and then use the 'sender' object to determine how to handle the event at runtime.
I have been using an expression tree to create a delegate and subscribe to any event with an Action<Object[]>, where all the event parameters are converted to an array of objects.
This has been working fine until now, that I need to subscribe to an event with an out/ref parameter and I need to set the value of this parameter.
Is there a way to use something similar to the expression tree and subscribe to any event, but still be able to set/return values to the object that raised the event?
Expression tree to create a delegate:
public static Delegate CreateProxyWithDynamicParameters(this EventInfo EventInfo, Action<object[]> Action)
{
var EventHandlerType = EventInfo.EventHandlerType;
var InvokeMethodInfo = EventHandlerType.GetMethod("Invoke");
var Parameters = InvokeMethodInfo.GetParameters().Select(Parameter => Expression.Parameter(Parameter.ParameterType, Parameter.Name)).ToArray();
var ConvertedParameters = Parameters.Select(Parameter => Expression.Convert(Parameter, typeof(object))).Cast<Expression>().ToArray();
var NewArrayInit = Expression.NewArrayInit(typeof(object), ConvertedParameters);
var Body = Expression.Call(Expression.Constant(Action), "Invoke", null, NewArrayInit);
var lambdaExpression = Expression.Lambda(Body, Parameters);
return Delegate.CreateDelegate(EventInfo.EventHandlerType, lambdaExpression.Compile(), InvokeMethodInfo.Name, ignoreCase: false);
}
Subscription with the delegate:
protected static void AddEvent<TMessage>(EventInfo eventInfo) where TMessage : EventArgs
{
void EventAction(object[] e)
{
// Run some event code.
}
var #delegate = eventInfo.CreateProxyWithDynamicParameters(EventAction);
DelegateDictionary[typeof(TMessage)] = #delegate;
eventInfo.AddEventHandler(x.Target, #delegate);
}
Edit:
Here is a vanilla simple. Where I need to set the HandlingCode parameter.
InventorApplication.ApplicationEvents.OnSaveDocument += ApplicationEvents_OnSaveDocument;
private void ApplicationEvents_OnSaveDocument(_Document DocumentObject, EventTimingEnum BeforeOrAfter, NameValueMap Context, out HandlingCodeEnum HandlingCode)
{
HandlingCode = HandlingCodeEnum.kEventHandled;
}
You are going to struggle to do this with Expression, because Expression likes to be able to run things via reflection for fallback, and it can't handle refs via reflection. You might be able to do it via raw IL, but... that's getting messier and messier.
Frankly, this event API is making life hard for you. If you control that API, I strongly suggest using the more idiomatic (object sender, SomeEventArgs args) where SomeEventArgs : EventArgs. Your SomeEventArgs would then have properties for all the things you're trying to pass as parameters, and the interested code can just do:
args.HandlingCode = ...
to assign a value.
I have seen several answers already but somehow I can't get mine to work. I want to dynamically use any of the events of various controls (textbox, checkbox, button, etc) and, preferably, assign them to one event handler. The handler should be assigned at runtime. Furthermore, I want to know in the handler which event triggered the handler.
I got this to work partially. Using a lambda expression I call my handler (EventAssistant) and pass an extra parameter (command) which contains the name of the event. It works for events that use type EventHandler. However, it won't work for events that expect a different handler such as type MouseEventHandler. It will fail to subscribe at AddEventHandler.
private void RegisterEventHandlers(Control ctl)
{
foreach (Command command in CommandList)
{
EventInfo eventInfo = ctl.GetType().GetEvent(command.Name);
EventHandler handler = (sender, args) =>
{
EventAssistant(sender, args, command);
};
eventInfo.AddEventHandler(ctl, handler);
}
}
public void EventAssistant(object sender, EventArgs e, Command c)
{
//do lots of other fun stuff
}
Based on C# passing extra parameters to an event handler?
As an alternative I tried to solve the problem with an Expression Tree as shown here: Why am I getting an Argument exception when creating event handler dynamically?
Apparently, the EventHandlerType can be retrieved from the EventInfo and used in a lambda expression.
But, whatever I do I always get an InvalidOperationException "Lambda Parameter not in scope".
private void RegisterEventHandlers(Control ctl)
{
foreach (Command command in CommandList)
{
EventInfo eventInfo = ctl.GetType().GetEvent(command.Name);
var sender = Expression.Parameter(typeof(object), "sender");
var e = Expression.Parameter(typeof(EventArgs), "e");
var c = Expression.Parameter(typeof(Command), "command");
Expression[] arg = new Expression[] { sender, e, c };
MethodInfo mi = this.GetType().GetMethod("EventAssistant");
var body = Expression.Call(Expression.Constant(this), mi, arg);
var lambda = Expression.Lambda(eventInfo.EventHandlerType, body, sender, e);
eventInfo.AddEventHandler(ctl, lambda.Compile());
}
}
What am I doing wrong with the Expression Tree?
Also, the first piece of code looks a lot more clean. Is it possible to get what I want using the first code sample?
In your second attempt, the variable c shouldn't be a ParameterExpression, but a ConstantExpression with the value set to the current command instead. With the current code, you are creating a handler, which essentially looks like this:
(_sender, _e) => this.EventAssistant(_sender, _e, _c)
// The expression expects "_c" to be a parameter of the lambda, which is why
// you're getting that exception
However, if you change
var c = Expression.Parameter(typeof(Command), "command");
to
var c = Expression.Constant(command);
the generated code will look (and work, of course) as expected:
(_sender, _e) => this.EventAssistant(_sender, _e, command)
I put "event" in quotes because I realize that it's a bit of syntax sugar, rather than a true type.
I have some events which are simply chained to matching events in another class. So when the event is raised, the passage is like
Raiser -> Proxy -> Subscriber
So in the Proxy class I have a common pattern like this:
Raiser.SomeEvent +=
(_, args) =>
{
if (this.SomeEvent != null)
this.SomeEvent(this, args);
};
To tidy up my code I wanted to move this out to another method that returns a new delegate that wraps the above event-calling code:
public static EventHandler GetHandlerDelegate(EventHandler handler, Object sender)
{
return
(_, args) =>
{
if (handler != null)
handler(sender, args);
};
}
And then in Proxy I can just do:
Raiser.SomeEvent += GetHandlerDelegate(this.SomeEvent, this);
Which is much neater.
Well this is fine as long as Subscriber doesn't decide to subscribe to Proxy.SomeEvent after the above call. Unfortunately I'm not passing the "event" around by reference as I'd hoped; I now understand that I'm just passing the invocation list, so when OtherClass.SomeEvent happens and that anonymous method is called and invokes the "event" (delegate) it was given, only the delegates that had been added to that event at the time I called GetHandlerDelegate() will be called. While that would actually suffice for my current situation, it's really not acceptable to code it that way.
I've read some other SO questions and I gather there is something called Reactive Extensions that might help, but at this time I'm looking for a simpler solution if there is one. (If not, I just won't do this.)
Is there another way I can accomplish what I'm trying to do, without said drawback?
If this question is unclear, please see my answer which hopefully helps clarify it.
EDIT: Okay, I think I get the point now. It's actually quite simple. You should be able to write the proxy to just have an event, and then make the proxy itself subscribe to the Raiser's event, like this (just for EventHandler - I'll come to that later on):
Proxy proxy = new Proxy();
raiser.SomeEvent += Proxy.Handler;
// Then in the subscriber...
proxy.ProxiedEvent += (whatever)
// And the proxy class...
public class Proxy
{
public event EventHandler ProxiedEvent;
public void Handler(object sender, EventArgs e)
{
EventHandler proxied = ProxiedEvent;
if (proxied != null)
{
// Or pass on the original sender if you want to
proxied(this, e);
}
}
}
Now, the difficulty here is getting it to work generically. I can't currently think of any way of doing that, although I'm somewhat distracted right now.
Is this the sort of thing you were thinking of, or does it at least help you think about things differently?
Since my original goal of doing:
Raiser.SomeEvent += GetHandlerDelegate(this.SomeEvent, this);
is impossible, I've compromised and come up with this:
Raiser.SomeEvent += (_, args) => RaiseEvent(this.SomeEvent, this, args);
Whereas GetHandlerDelegate() would return a delegate which raises the event, RaiseEvent() simply (you guessed it) raises the event.
public static void RaiseEvent(EventHandler _event, Object sender, EventArgs args)
{
if (_event != null)
_event(sender, args);
}
And to support events using custom EventArgs:
public static void RaiseEvent<TArgs>(EventHandler<TArgs> _event, Object sender, TArgs args)
where TArgs : EventArgs
{
if (_event != null)
_event(sender, args);
}
I've put these methods in a static helper class, so the actual call is slightly uglier; here's an example:
ViewControl.OpenFilesetClick += (_, args) => EventHelper.Raise(OpenFilesetClick, this, args);
(I also renamed the method to Raise() and dropped the optional this from the event name being passed).
But I'm not entirely convinced if this is worthwhile, considering the alternative was arguably easier to read:
ViewControl.OpenFilesetClick += (_, args) =>
{
if (OpenFilesetClick != null)
OpenFilesetClick(this, args);
};
Anyway, it was an interesting way to learn more about how events and delegates work (or how they don't work).
We've got a client calling off to a TIBCO EMS queue and are wiring up the events like this:
var msgConsumer = _session.CreateConsumer(responseQueue);
var response = Observable.FromEvent<EMSMessageHandler,EMSMessageEventArgs>
(h => msgConsumer.MessageHandler += h, h => msgConsumer.MessageHandler -= h)
.Where(arg => arg.Message.CorrelationID == message.MessageID);
When I call response.Subscribe(...) I get System.ArgumentException "Error binding to target method."
I can make it work with Observable.FromEventPattern<EMSMessageEventArgs>(msgConsumer, "MessageHandler") but then it's got the event as a string and just not as clean.
Also I have IObservable<EventPattern<EMSMessageEventArgs>> rather than IObservable<EMSMessageEventArgs>
What I'd like to understand is: when should I use FromEvent over FromEventPattern? It seems a bit trial and error.
"Use FromEvent for events structurally don't look like a .NET event pattern (i.e. not based on sender, event args), and use FromEventPattern for the pattern-based ones." - Bart De Smet (Rx team)
To elaborate on this a bit further, you can typically determine when to choose one of the FromEvent vs FromEventPattern by the type of event used in the class you're trying to observe. Use FromEventPattern when your event is of type EventHandler or the generic EventHandler<T>. Use FromEvent when you're using a custom, non-generic event handler type.
The following examples are lifted directly from the Rx wiki, which has lots of good examples- 101 of them to be exact.
FromEventPattern (with generic EventHandler):
class ObserveEvent_Generic
{
public class SomeEventArgs : EventArgs { }
public static event EventHandler<SomeEventArgs> GenericEvent;
static void Main()
{
// To consume GenericEvent as an IObservable:
IObservable<EventPattern<SomeEventArgs>> eventAsObservable = Observable.FromEventPattern<SomeEventArgs>(
ev => GenericEvent += ev,
ev => GenericEvent -= ev );
}
}
FromEvent:
class ObserveEvent_NonGeneric
{
public class SomeEventArgs : EventArgs { }
public delegate void SomeNonGenericEventHandler(object sender, SomeEventArgs e);
public static event SomeNonGenericEventHandler NonGenericEvent;
static void Main()
{
// To consume NonGenericEvent as an IObservable, first inspect the type of EventArgs used in the second parameter of the delegate.
// In this case, it is SomeEventArgs. Then, use as shown below.
IObservable<IEvent<SomeEventArgs>> eventAsObservable = Observable.FromEvent(
(EventHandler<SomeEventArgs> ev) => new SomeNonGenericEventHandler(ev),
ev => NonGenericEvent += ev,
ev => NonGenericEvent -= ev);
}
}
Apparently the new syntax has changed a bit. The current syntax is the following:
// get events as observable
var events = Observable.FromEvent<MouseButtonEventHandler, MouseEventArgs>(
handler => MouseUp += handler, handler => MouseUp -= handler);
// subscribe to events
var subscription = events.Subscribe(args => OnMouseUp()));
// always dispose subscriptions!
subscription.Dispose();