Rewrite lambda extension method - c#

I've created an extension method that works just like I wanted. I've noticed that somehow the party and property parameters are 'copied' into the lambda expression. This way I do not need to maintain a custom list of editor/party/property associations.
However, I need to reset the ButtonEdit's ButtonClick event. Since this one is anonymous I cannot use the -= opertor either.
So, my question is - how do I rewrite this method so that the delegate can be removed? Or, which other approach can I use to handle a specific event handler with extra parameters (such as party and property)?
private static void SetupAddressButtonClickEvent(this ButtonEdit editor, Party party, string property)
{
editor.SetAddressDisplayText(party, property);
editor.ButtonClick += (sender, e) =>
{
party.ShowAddressLookupDialog(property);
editor.SetAddressDisplayText(party, property);
};
}
Thank you,
Stefan

Action<object,EventArgs> myaction = (sender, e) =>
{
party.ShowAddressLookupDialog(property);
editor.SetAddressDisplayText(party, property);
};
editor.ButtonClick += myaction;
editor.ButtonClick -= myaction;
edit option 2 could be:
class MyEventHandler
{
... _property;
... _party;
... _editor;
public MyEventHandler(... property, ... party, ... editor)
{
_property = property;
_party = party;
_editor = editor;
}
public void Handler(object sender, EventArgs e)
{
_party.ShowAddressLookupDialog(_property);
_editor.SetAddressDisplayText(_party, _property);
}
}
and then use it like this:
var handler = new MyEventHandler(party,property,editor);
editor.ButtonClick += handler.Handler;
I'm not sure how much this will help you because I don't 100% understand what you're trying to solve.

Related

Is there a shorthand way to declare a control and subscribe event handlers to it?

Usually when I'm using C# with XAML or Xamarin I tend to create objects and assign properties directly like this:
StackLayout mainStackLayout = new StackLayout() { Padding = 15 };
mainStackLayout.Children.Add(new Button() {
Text = "Clicky!",
FontSize = 12
}
Then when I realize I want to assign an event handler to it later, I come back and have to do:
StackLayout mainStackLayout = new StackLayout() { Padding = 15 };
var btn = new Button() { Text = "Clicky!", FontSize = 12 }
btn.Clicked += (sender, args) => { DisplayAlert("Hello!"); }
mainStackLayout.Children.Add(btn)
I really enjoy using lambdas for this (and in general) but I always wondered if there is a way to combine the two methods and subscribe an event handler to the control without having to use a temp variable?
No, an event cannot be assigned to in an initializer.
I never understood why assigning them has to be done through the += operator
That is the main purpose of the event keyword. An event is a special property around a delegate, providing extra strong encapsulation. += and -= are the only operations allowed.
The thing that irks you here is that delegates/events are multi-cast. Allowing a simple foo.Click = myHandler; could potentially overwrite other subscribers. += means adding to a list. And += is not compatible with the initializer syntax.
So a special feature would be needed, a special case would be needed to allow += in the initializer or to redefine = to mean += for events there.
But, as the saying goes, "features start with a score of -100 ..."
There is an ugly workaround for this - taking advantage of the fact properties can have setter-logic. This only works for types that you have control over, however:
public class Button2 : Button
{
public EventHandler Clicked2
{
get { return this.Clicked; }
set
{
this.Clicked += value;
}
}
}
Used like so:
Button2 btn = new Button2()
{
Text = "Clicky",
FontSize = 12,
Clicked2 = new EventHandler( (s,e) => this.DisplayAlert("Hello!") )
};
I've just discovered another hack that might work.
In C# 6, the Collection Initialization syntax previously required the object to implement IEnumerable<T> and have a method named Add (using duck-typing, it does not need to implement IList<T>).
In C# 7, the Add method requirement has been loosened to now accept an extension method, though the IEnumerable<T> requirement persists.
So we can use an extension-method Add to "inject" our multi-statement function (to add an event-handler), we just need to find a way via a member that uses IEnumerable<T>.
Now while System.Windows.Controls.Control does not implement IEnumerable<T> itself, we can examine the members of the inheritance hierarchy and see we can latch on to UIElement.CommandBindings - or any other collection member that has an internal reference to the object we want, such as InputBindings (it is entirely coincidental that InputBindings could be used to set-up event-handlers anyway, but that's a red-herring).
So if we have this:
static class Extensions
{
private static readonly FieldInfo _ownerField = typeof(InputBindingCollection).GetField("_owner", BindingFlags.Instance | BindingFlags.NonPublic);
public static void Add(this InputBindingCollection list, String eventName, Delegate handler)
{
Object ownerValue = _ownerField.GetValue( list );
DependencyObject owner = (DependencyObject)ownerValue;
// We assume it's a XAML Control instance:
Control ownerControl = (Control)owner;
switch( eventName )
{
case "click":
ownerControl.MouseDown += (MouseButtonEventHandler)handler;
break;
case "keydown":
ownerControl.KeyDown += (KeyEventHandler)handler;
break;
// etc...
}
}
}
...we can now add event-handlers - or invoke any multi-statement code - using initializer syntax!
In your case, I can now add event-handlers like so:
Button btn = new Button()
{
FontSize = 12,
Content = "button text",
InputBindings =
{
{ "click", (MouseButtonEventHandler)( (s, e) => DisplayFoo("clicked!") ) }
}
};
This is a huge hack that uses Reflection to get at the target UIElement object - I think it can be improved by using another approach to get the UIElement - perhaps by passing-in a sneaky subclass of InputBinding into the InputBindingCollection to catch it via some callback.
The explicit naming of events can also probably be improved too.
Update:
I see that most XAML components (at least in WPF) use RoutedEvent objects to identify events - that means we can make this both simpler and more strongly-typed without resorting to string hacks:
static class Extensions
{
private static readonly FieldInfo _ownerField = typeof(InputBindingCollection).GetField("_owner", BindingFlags.Instance | BindingFlags.NonPublic);
public static void Add(this InputBindingCollection list, RoutedEvent routedEvent, RoutedEventHandler handler)
{
UIElement owner = (UIElement)_ownerField.GetValue( list );;
owner.AddHandler( routedEvent, handler );
}
}
Event-handlers can be added like so:
Button btn = new Button()
{
FontSize = 12,
Content = "button text",
InputBindings =
{
{ Button.ClickEvent, (s, e) => DisplayFoo("clicked!") },
{ Button.LoadedEvent, (s, e) => DisplayFoo("loaded!") }
}
};
Because of method overloading, we can still support non-RoutedEvent-type events using my previous strategy. **But we can also generalize this to accept any Action<T> callback:
static class Extensions
{
private static readonly FieldInfo _ownerField = typeof(InputBindingCollection).GetField("_owner", BindingFlags.Instance | BindingFlags.NonPublic);
public static void Add(this InputBindingCollection list, RoutedEvent routedEvent, RoutedEventHandler handler)
{
UIElement owner = (UIElement)_ownerField.GetValue( list );;
owner.AddHandler( routedEvent, handler );
}
public static void Add(this InputBindingCollection list, String eventName, Delegate handler)
{
UIElement owner = (UIElement)_ownerField.GetValue(list);
EventInfo eventInfo = owner.GetType().GetEvent( eventName, BindingFlags.Instance | BindingFlags.Public );
eventInfo.AddEventHandler( owner, handler );
}
public static void Add<T>(this InputBindingCollection list, Action<T> callback)
where T : UIElement
{
T owner = (T)_ownerField.GetValue(list);
callback( owner );
}
}
Which means we can do this:
Button btn = new Button()
{
FontSize = 12,
Content = "button text",
InputBindings =
{
{ Button.ClickEvent, (s, e) => DisplayFoo("clicked!") },
{ Button.LoadedEvent, (s, e) => DisplayFoo("loaded!") },
{ nameof(Button.ContextMenuClosing), (ContextMenuEventHandler)((s,e) => DisplayFoo("context menu closing")) },
{ (Button b) => b.Click += (s, e) => DisplayFoo("clicked 3!") },
{ (Button b) =>
{
b.Click += (s, e) => DisplayFoo("clicked 3!");
b.ContextMenuClosing += (s, e) => DisplayFoo("context menu closing 2");
} }
}
};
Note you need to specify the types of the callback parameters so type-inference can fill-in the T for the Action<T>.
Y'know - I'm surprised at how well this works. I might even use this in my own projects.
Update 2
I spent some time to see if it could support scenarios where Reflection isn't available - by avoiding the need to reflect InputBindingCollection._owner by subclassing InputBinding, however the DependencyObject for the inheritnace-context just isn't exposed anywhere unfortunately.
I took another stab by looking at BindingGroup which is the only class that exposes the InheritanceContext via BindingGroup.Owner (which all Control types have), but unfortunately there's no way to get to an IEnumerable with which to attach our Add method.
Update 3
I realised there's another approach that can be used, taking advantage of the fact the "emitted" value of an object-initializer expression is the object itself (as opposed to void), so you can just do this:
static class Extensions
{
public static T Exec<T>(this T item, Action<T> callback)
{
callback( item );
return item;
}
}
Button btn = new Button()
{
FontSize = 12,
Context = "button text"
}.Exec( b => {
b.Click + (s,e) => ... ;
b.DoubleClick + (s,e) => ... ;
} )

Adding and removing events in one method, c#

Sometimes I need to add and remove events from bunch of controls. And I always do that with one method for adding events:
private void AddEvents(){
textBox.TextChanged += TextChanged;
integerUpDown.ValueChanged += ValueChanged;
colorPicker.SelectedColorChanged += ColorChanged;
//... and so on
}
And same one for removing events:
private void RemoveEvents(){
textBox.TextChanged -= TextChanged;
integerUpDown.ValueChanged -= ValueChanged;
colorPicker.SelectedColorChanged -= ColorChanged;
//... and so on
}
I'm using different types of controls and different types of EventArgs. I'd like to compress it to one method, something like:
private void RemoveEvents(bool add){
textBox.TextChanged add ? += : -= TextChanged;
integerUpDown.ValueChanged add ? += : -= ValueChanged;
//or method approach
ManageEvent(colorPicker.SelectedColorChanged, ColorChanged, add);
//... and so on
}
But that's not possible with ? operator. Is there a way to do it?
I think this is neat.
First, define the following interface and class:
public interface IEventHolder
{
void Attach();
void Detach();
}
public class EventHolder<H> : IEventHolder
{
private Action<H> _add;
private Action<H> _remove;
private H _handler;
public EventHolder(Action<H> add, Action<H> remove, H handler)
{
_add = add;
_remove = remove;
_handler = handler;
}
public void Attach() { _add(_handler); }
public void Detach() { _remove(_handler); }
}
Now you can define this private field:
private List<IEventHolder> _eventHolders = new List<IEventHolder>();
In the Form_Load event I've written this code:
private void Form1_Load(object sender, EventArgs e)
{
_eventHolders.Add(new EventHolder<EventHandler>(h => textBox1.TextChanged += h, h => textBox1.TextChanged -= h, textBox1_TextChanged));
_eventHolders.Add(new EventHolder<EventHandler>(h => numericUpDown1.ValueChanged += h, h => numericUpDown1.ValueChanged -= h, numericUpDown1_ValueChanged));
_eventHolders.Add(new EventHolder<MouseEventHandler>(h => textBox2.MouseMove += h, h => textBox2.MouseMove -= h, textBox2_MouseMove));
_eventHolders.ForEach(eh => eh.Attach());
}
Notice that the line _eventHolders.ForEach(eh => eh.Attach()); attaches all events.
My handlers look like this:
private void textBox1_TextChanged(object sender, EventArgs e)
{ }
private void numericUpDown1_ValueChanged(object sender, EventArgs e)
{
this.textBox1.Text = numericUpDown1.Value.ToString();
}
private void textBox2_MouseMove(object sender, MouseEventArgs e)
{
_eventHolders.ForEach(eh => eh.Detach());
}
The textBox2_MouseMove handler detaches all of the events in one go.
I've tested this and it works like a treat.
As you want to avoid having to specify both adding and removing a handler, there is only reflection left.
private static void ManageEvent(object target, string evnt, EventHandler handler, bool add)
{
var ei = target.GetType().GetEvent(evnt);
if(add)
{
ei.AddEventHandler(target, handler);
}else{
ei.RemoveEventHandler(target, handler);
}
}
This method finds the target event info on a given instance, and based on a condition invokes either the add or remove method. You can also make this generic, but then you would have to specify the type of the delegate. This assumes EventHandler, which is quite common.
ManageEvent(textBox, "TextChanged", TextChanged, add);
In C# 6, you can also use nameof(textBox.TextChanged) instead of "TextChanged" which makes your code easier to refactor.
Please note that reflection performs mostly slow, so consider using the approach shown in other answers if you have to call this method a lot.
This all is necessary because the events are located in another class, and you cannot thus access directly their backing field. However, if the event is located in your class, you can pass it as a reference to a similar helper method:
private static void ManageEvent<TDel>(ref TDel source, TDel value, bool add) where TDel : class
{
if(add)
{
source = (TDel)(object)Delegate.Combine((Delegate)(object)source, (Delegate)(object)value);
}else{
source = (TDel)(object)Delegate.Remove((Delegate)(object)source, (Delegate)(object)value);
}
}
This does exactly what normal event methods do, either combine or remove events.
ManageEvent(ref MyEvent, MyEventHandler, add);
The conditional operation returns a value based on the statement's result. Returning "+=" is definitely not valid, since you are basically subscribing to the event. https://msdn.microsoft.com/en-us/library/ty67wk28.aspx
However, why don't you take the simpler approach?
private void AddOrRemoveEvents(bool add)
{
if (add)
{
AddEvents();
}
else
{
RemoveEvents();
}
}
One line if statement to the rescue! There will be a little bit of repetition but none the less it is a one liner
if (add) textbox.TextChanged += TextChanged; else textbox.TextChanged -= TextChanged;

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

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.

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();
}
};

C# Dynamic Event Subscription

How would you dynamically subscribe to a C# event so that given a Object instance and a String name containing the name of the event, you subscribe to that event and do something (write to the console for example) when that event has been fired?
It would seem using Reflection this isn't possible and I would like to avoid having to use Reflection.Emit if possible, as this currently (to me) seems like the only way of doing it.
/EDIT: I do not know the signature of the delegate needed for the event, this is the core of the problem
/EDIT 2: Although delegate contravariance seems like a good plan, I can not make the assumption necessary to use this solution
You can compile expression trees to use void methods without any arguments as event handlers for events of any type. To accommodate other event handler types, you have to map the event handler's parameters to the events somehow.
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
class ExampleEventArgs : EventArgs
{
public int IntArg {get; set;}
}
class EventRaiser
{
public event EventHandler SomethingHappened;
public event EventHandler<ExampleEventArgs> SomethingHappenedWithArg;
public void RaiseEvents()
{
if (SomethingHappened!=null) SomethingHappened(this, EventArgs.Empty);
if (SomethingHappenedWithArg!=null)
{
SomethingHappenedWithArg(this, new ExampleEventArgs{IntArg = 5});
}
}
}
class Handler
{
public void HandleEvent() { Console.WriteLine("Handler.HandleEvent() called.");}
public void HandleEventWithArg(int arg) { Console.WriteLine("Arg: {0}",arg); }
}
static class EventProxy
{
//void delegates with no parameters
static public Delegate Create(EventInfo evt, Action d)
{
var handlerType = evt.EventHandlerType;
var eventParams = handlerType.GetMethod("Invoke").GetParameters();
//lambda: (object x0, EventArgs x1) => d()
var parameters = eventParams.Select(p=>Expression.Parameter(p.ParameterType,"x"));
var body = Expression.Call(Expression.Constant(d),d.GetType().GetMethod("Invoke"));
var lambda = Expression.Lambda(body,parameters.ToArray());
return Delegate.CreateDelegate(handlerType, lambda.Compile(), "Invoke", false);
}
//void delegate with one parameter
static public Delegate Create<T>(EventInfo evt, Action<T> d)
{
var handlerType = evt.EventHandlerType;
var eventParams = handlerType.GetMethod("Invoke").GetParameters();
//lambda: (object x0, ExampleEventArgs x1) => d(x1.IntArg)
var parameters = eventParams.Select(p=>Expression.Parameter(p.ParameterType,"x")).ToArray();
var arg = getArgExpression(parameters[1], typeof(T));
var body = Expression.Call(Expression.Constant(d),d.GetType().GetMethod("Invoke"), arg);
var lambda = Expression.Lambda(body,parameters);
return Delegate.CreateDelegate(handlerType, lambda.Compile(), "Invoke", false);
}
//returns an expression that represents an argument to be passed to the delegate
static Expression getArgExpression(ParameterExpression eventArgs, Type handlerArgType)
{
if (eventArgs.Type==typeof(ExampleEventArgs) && handlerArgType==typeof(int))
{
//"x1.IntArg"
var memberInfo = eventArgs.Type.GetMember("IntArg")[0];
return Expression.MakeMemberAccess(eventArgs,memberInfo);
}
throw new NotSupportedException(eventArgs+"->"+handlerArgType);
}
}
static class Test
{
public static void Main()
{
var raiser = new EventRaiser();
var handler = new Handler();
//void delegate with no parameters
string eventName = "SomethingHappened";
var eventinfo = raiser.GetType().GetEvent(eventName);
eventinfo.AddEventHandler(raiser,EventProxy.Create(eventinfo,handler.HandleEvent));
//void delegate with one parameter
string eventName2 = "SomethingHappenedWithArg";
var eventInfo2 = raiser.GetType().GetEvent(eventName2);
eventInfo2.AddEventHandler(raiser,EventProxy.Create<int>(eventInfo2,handler.HandleEventWithArg));
//or even just:
eventinfo.AddEventHandler(raiser,EventProxy.Create(eventinfo,()=>Console.WriteLine("!")));
eventInfo2.AddEventHandler(raiser,EventProxy.Create<int>(eventInfo2,i=>Console.WriteLine(i+"!")));
raiser.RaiseEvents();
}
}
It's not a completely general solution, but if all your events are of the form
void Foo(object o, T args) , where T derives from EventArgs, then you can use delegate contravariance to get away with it. Like this (where the signature of KeyDown is not the same as that of Click) :
public Form1()
{
Button b = new Button();
TextBox tb = new TextBox();
this.Controls.Add(b);
this.Controls.Add(tb);
WireUp(b, "Click", "Clickbutton");
WireUp(tb, "KeyDown", "Clickbutton");
}
void WireUp(object o, string eventname, string methodname)
{
EventInfo ei = o.GetType().GetEvent(eventname);
MethodInfo mi = this.GetType().GetMethod(methodname, BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
Delegate del = Delegate.CreateDelegate(ei.EventHandlerType, this, mi);
ei.AddEventHandler(o, del);
}
void Clickbutton(object sender, System.EventArgs e)
{
MessageBox.Show("hello!");
}
It is possible to subscribe to an event using Reflection
var o = new SomeObjectWithEvent;
o.GetType().GetEvent("SomeEvent").AddEventHandler(...);
http://msdn.microsoft.com/en-us/library/system.reflection.eventinfo.addeventhandler.aspx
Now here is going to be the problem that you are going to have to solve. The delegates required for each event handler will have different signatures. You are going to have to find away to create these methods dynamically, which probably means Reflection.Emit, or you are going to have to limit your self to a certain delegate so that you can handle it with compiled code.
Hope this helps.
public TestForm()
{
Button b = new Button();
this.Controls.Add(b);
MethodInfo method = typeof(TestForm).GetMethod("Clickbutton",
BindingFlags.NonPublic | BindingFlags.Instance);
Type type = typeof(EventHandler);
Delegate handler = Delegate.CreateDelegate(type, this, method);
EventInfo eventInfo = cbo.GetType().GetEvent("Click");
eventInfo.AddEventHandler(b, handler);
}
void Clickbutton(object sender, System.EventArgs e)
{
// Code here
}
Try LinFu--it has a universal event handler that lets you bind to any event at runtime. For example, here's you you can bind a handler to the Click event of a dynamic button:
// Note: The CustomDelegate signature is defined as:
// public delegate object CustomDelegate(params object[] args);
CustomDelegate handler = delegate
{
Console.WriteLine("Button Clicked!");
return null;
};
Button myButton = new Button();
// Connect the handler to the event
EventBinder.BindToEvent("Click", myButton, handler);
LinFu lets you bind your handlers to any event, regardless of the delegate signature. Enjoy!
You can find it here:
http://www.codeproject.com/KB/cs/LinFuPart3.aspx
I recently wrote a series of blog posts describing unit testing events, and one of the techniques I discuss describes dynamic event subscription. I used reflection and MSIL (code emitting) for the dynamic aspects, but this is all wrapped up nicely. Using the DynamicEvent class, events can be subscribed to dynamically like so:
EventPublisher publisher = new EventPublisher();
foreach (EventInfo eventInfo in publisher.GetType().GetEvents())
{
DynamicEvent.Subscribe(eventInfo, publisher, (sender, e, eventName) =>
{
Console.WriteLine("Event raised: " + eventName);
});
}
One of the features of the pattern I implemented was that it injects the event name into the call to the event handler so you know which event has been raised. Very useful for unit testing.
The blog article is quite lengthy as it is describing an event unit testing technique, but full source code and tests are provided, and a detailed description of how dynamic event subscription was implemented is detailed in the last post.
http://gojisoft.com/blog/2010/04/22/event-sequence-unit-testing-part-1/
What you want can be achieved using dependency injection. For example Microsoft Composite UI app block does exactly what you described
This method adds to an event, a dynamic handler that calls a method OnRaised, passing the event parameters as an object array:
void Subscribe(object source, EventInfo ev)
{
var eventParams = ev.EventHandlerType.GetMethod("Invoke").GetParameters().Select(p => Expression.Parameter(p.ParameterType)).ToArray();
var eventHandler = Expression.Lambda(ev.EventHandlerType,
Expression.Call(
instance: Expression.Constant(this),
method: typeof(EventSubscriber).GetMethod(nameof(OnRaised), BindingFlags.NonPublic | BindingFlags.Instance),
arg0: Expression.Constant(ev.Name),
arg1: Expression.NewArrayInit(typeof(object), eventParams.Select(p => Expression.Convert(p, typeof(object))))),
eventParams);
ev.AddEventHandler(source, eventHandler.Compile());
}
OnRaised has this signature:
void OnRaised(string name, object[] parameters);
Do you mean something like:
//reflect out the method to fire as a delegate
EventHandler eventDelegate =
( EventHandler ) Delegate.CreateDelegate(
typeof( EventHandler ), //type of event delegate
objectWithEventSubscriber, //instance of the object with the matching method
eventSubscriberMethodName, //the name of the method
true );
This doesn't do the subscription, but will give to the method to call.
Edit:
Post was clarified after this answer, my example won't help if you don't know the type.
However all events in .Net should follow the default event pattern, so as long as you've followed it this will work with the basic EventHandler.

Categories