I am trying to write a library that allows the attaching of events using attributes, and I want to create a set of convenience method signatures.
For example, a button will have a Click event with a handler signature of void(object, EventArgs).
I have already mapped the methods that match this signature directly:
// the object that raises the event
Object eventSource = ...;
EventInfo evnt = ...;
// the object with the target method
Object target = ...;
MethodInfo method = ...;
// create and attach delegate
var del Delegate.CreateDelegate(evnt.EventHandlerType, target, method);
evnt.AddEventHandler(eventSource, del);
This works well as long as the methods are the same/compatible, even when detaching an event:
evnt.RemoveEventHandler(eventSource, del);
However, I would like to also be able to map parameterless methods.
Is it possible to create a delegate that will accept any arguments, but then ignore them, and invoke a desired method on an object?
For an example, in the working bit, I can do:
// the method
void MyClickMethod(object sender, EventArgs e)
{
}
// the execution
var evnt = myButton.GetType().GetEvent("Click");
var method = this.GetType().GetMethod("MyClickMethod")
AttachEvent(/*eventSource*/ myButton, /*evnt*/ evnt, /*target*/ this, /*method*/ method);
But, I would also like to be able to attach this method:
void MyClickMethod()
{
}
My logic here is that we can attach any parameterless method on any type to any event. This is often very helpful. It is important that I be able to detach any events.
What goes on in my mind, would to somehow create a delegate that does this:
eventSource.Event += (sender, e) => {
target.method();
};
Is there a way to do this cleanly?
Related
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 the following:
public String AttachService(string whereClauseParam)
{
//Get Client object here
Client c = new Client();
string cookieFromRequest = WebOperationContext.Current.IncomingRequest.Headers[HttpRequestHeader.Cookie];
tokenInfo.TryGetValue(cookieFromRequest, out c);
string[] arr = new string[] { };
c.AttachedServiceStatus += OnAttachedServiceStatus;
string whereClause = whereClauseParam.ToString();
//c.AttachService("binding.interface='query_em'", 8799989);
return string.Format("attached");
}
//Handler code below:
public string OnAttachedServiceStatus(Client sender, ClientServiceAttachedStatus status)
{
if (status.AttachStatus == AttachedStatus.Connected && status.ServiceAttachStatus == ServiceAttachStatus.Attached)
{
//update the Client object in Dictionary
Client c = new Client();
var ou = tokenInfo.First(x => x.Value == sender);
tokenInfo.TryGetValue(ou.Key.ToString(), out c);
tokenInfo.TryRemove(ou.Key.ToString(), out c);
tokenInfo.TryAdd(ou.Key.ToString(), sender);
string[] statusInfoT = new string[200];
statusInfoT[0] = status.ServiceId.ToString();
statusInfoT[1] = status.AttachStatus.ToString();
statusInfoT[2] = status.ServiceAttachStatus.ToString();
statusInfoT[3] = status.VirtualServiceId.ToString();
statusInfoT[4] = status.AttachToken.ToString();
statusInfo.TryAdd(ou.Key.ToString(), statusInfoT);
//update the UI with a Dispatch - TO BE DONE
}
return "Connected";
}
The above AttachService method has a handler "OnAttachedServiceStatus" attached to an event "AttachedServiceStatus".
As long as the OnAttachedServiceStatus return void, it all works well. However, i now need to have the Handler OnAttachedServiceStatus to return a string but i'm not able to attach the handler correctly.
I'm thinking of using the Func delegate but not sure how to use it.
Please Help!
First of all, signature of event handler is defined by event's delegate type. If that delegate returns void, then you cannot attach any other methods. Both parameters of method and return value should match signature of even's delegate. I believe AttachedServiceStatus uses delegate which returns void. Something like that:
public delegate void Action<T1, T2>(T1 arg1, T2 arg2)
And event is
public event Action<Client, ClientServiceAttachedStatus> AttachedServiceStatus
But what if you'll use delegate which returns value? E.g.
public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2)
You can declare event as
public event Func<Client, ClientServiceAttachedStatus> AttachedServiceStatus
But it makes no sense. Because event is a delegate. When you attach handler, you are actually combining delegates, creating something like list of delegates (invocation list). This list contains all attached handlers. When you raise event, all handlers in invocation list are invoked one by one, and only result of last invoked handler is returned. Order of invokation is not determined. So you even don't know which handler returned value.
(*) Though it is still possible to get all results if you will invoke each handler manually instead of raising event. See Servy comment below
UPDATE
I want the handler "OnAttachedServiceStatus" to return a string back
to Caller "AttachService" but i cannot get the following correct
When you attach handler to event, handler is not executed. It just added to invocation list of event. Event handler will be executed when Client will raise event. So AttachService is not a caller here. Client is a caller. And you cannot return string back to AttachService. After attaching handler to event, code will exit AttachService method. Some time later event will be raised, and handler will be executed, but it will not be related to AttachService method.
I'm not sure you're understanding how events work.
Client c = new Client();
// ...
c.AttachedServiceStatus += OnAttachedServiceStatus;
OnAttachedServiceStatus is not being called here. Instead, this is telling the Client object to invoke the OnAttachedServiceStatus method whenever it raises the AttachedServiceStatus event, which may happen at any point in the future. It's like you telling a racer "When I say 'go', run as fast as you can to the finish line then tell me how many steps it took you to get there". The racer doesn't immediately start running at that point, nor do they tell you how many steps it took; they get in the ready position and wait. When you say "go", that's when they execute your instructions and start running. By the time you get their response, it's well after you gave him the instruction to wait.
From the looks of things, you're attempting to establish a remote connection and are wanting confirmation back from the server that a connection has indeed been established. If using events to convey that information, you'll want to use the EventArgs to carry it. You should be able to achieve that with something like this:
Client side:
public String ConnectToServer(string whereClauseParam)
{
//Create Server object here
Server s = new Server();
s.AttachedServiceStatus += OnAttachedServiceStatus;
s.AttachService(this, whereClauseParam, 8799989);
}
public void OnAttachedServiceStatus (object sender, ClientServiceAttachedEventArgs e)
{
if (e.AttachStatus == AttachedStatus.Connected && e.ServiceAttachStatus == ServiceAttachStatus.Attached)
{
// Update the UI with the message from the server.
MessageBox.Show(e.Message);
// If you need to do something else with the server in response, you can do this:
((Server)sender).Foo("bar");
}
}
And on the server side, define the custom EventArgs class for your event:
// By making this inherit from EventArgs, we can use the built-in EventHandler<T> delegate for the event itself.
public class ClientServiceAttachedEventArgs : EventArgs
{
public AttachedStatus AttachStatus { get; set; }
public ServiceAttachStatus ServiceAttachStatus { get; set; }
public string Message { get; set; }
// You can put in as many properties as you want to carry the information back from the server.
}
And put this in your Server class:
public event EventHandler<ClientServiceAttachedEventArgs> AttachedServiceStatus;
public String AttachService(Client client, string whereClauseParam, int code)
{
// Do what you need to do to register the client.
//...
// Assuming everything went as planned, fire the event.
// First, construct the EventArgs with information about the results of the connection.
ClientServiceAttachedEventArgs e = new ClientServiceAttachedEventArgs();
e.AttachStatus = AttachedStatus.Connected;
e.ServiceAttachStatus = ServiceAttachStatus.Attached;
e.Message = "Attached";
// This is where your OnAttachedServiceStatus method in the client finally gets called. If the event handler were returning a string, this is where it would be returned to and I can't imagine this does you any good.
AttachedServiceStatus(this, e);
}
This is a fairly basic implementation and your situation is probably more complex but it should point you in the right direction. The important thing to note is that the string that you wanted returned back to the client is coming through the event as part of the ClientServiceAttachedEventArgs, along with your status enums. This is the preferred way of sending information through events.
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)
This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
How to pass an event to a method?
Is it possible to pass an event as a parameter to a method?
For example, the following method subscribes to the event, does work, and unsubscribes from the event:
void SubscribeDoAndUnsubscribe<TElement, TEventArgs>(
IEnumerable<TElement> elements,
??? elementEvent)
where TEventArgs: EventArgs
{
EventHandler<TEventArgs> handler = (sender, e) => { /* Handle an event */ };
foreach (var element in elements)
{
// Subscribe somehow
element.elementEvent += handler
}
// Do things
foreach (var element in elements)
{
// Unsubscribe somehow
element.elementEvent -= handler
}
}
Client code:
var elements = new [] { new Button(), new Button() };
SubscribeDoAndUnsubscribe(elements, ??? /* e => e.Click */);
If it's not possible, how do I achieve the similar logic in other ways? Shall I pass pair of delegates for subscribe/unsubscribe methods?
You have in fact discovered that events are not "first class" in C#; you cannot pass around an event as data. You can pass around a delegate to a method associated with a receiver as a first-class object by making a delegate. You can pass around a reference to any variable as a (mostly) first-class object. (I say "mostly" because references to variables cannot be stored in fields, stored in arrays, and so on; they are highly restricted compared to other kinds of data.) You can pass around a type by obtaining its Type object and passing that around.
But there is no way to directly pass around as data an event, property, indexer, constructor or destructor associated with a particular instance. The best you can do is to make a delegate (or pair of delegates) out of a lambda, as you suggest. Or, obtain the reflection object associated with the event and pass that around, along with the instance.
No, unfortunately not.
If you look at Reactive Extensions, that suffers from a similar problem. Three options they use (IIRC - it's been a while since I've looked):
Pass in the corresponding EventInfo and call it with reflection
Pass in the name of the event (and the target if necessary) and call it with reflection
Pass in delegates for subscription and unsubscription
The call in the latter case would be something like:
SubscribeAndDoUnsubscribe(elements,
handler => e.Click += handler,
handler => e.Click -= handler);
and the declaration would be:
void SubscribeDoAndUnsubscribe<TElement, TEventArgs>(
IEnumerable<TElement> elements,
Action<EventHandler<TEventArgs>> subscription,
Action<EventHandler<TEventArgs>> unsubscription)
where TEventArgs: EventArgs
You're trying to get around type safety, and you can't do so without using reflection. I'll show you an even simpler example of what you're trying to do.
void DoSomethingOnSomethingElse(T obj, Action method)
{
obj.method();
}
C# doesn't work this way. How does the compiler know that all Ts have the method method? It doesn't, and can't. Similarly, not every TElement in your code will have an event Click for example.
It sounds like you just want to set a single use event handler on a set of objects. You can do this quite easily...
EventHandler handler = null;
handler = (s,e) =>
{
DoSomething(e);
var b = (Button) s;
b.Click -= handler;
}
foreach (var button in buttons)
{
button.Click += handler;
}
This, obviously, only works with buttons, but as I write this, I see Jon Skeet has shown you a more general solution, so I'll end here.
m having a winForm and in that m using BackGroundWorker control for keeping Form GUI active.
Now i'm accessing datagridview from backgroundworker_doWork() method, so i created a delegate method below:
delegate void updateGridDelegate();
private void invokeGridControls()
{
if (autoGridView.InvokeRequired)
{
updateGridDelegate delegateControl = new updateGridDelegate(invokeGridControls);
autoGridView.Invoke(delegateControl);//here i need to do something to access autoGridView.Rows.Count
}
}
and in the backgroundworker_DoWork() event m accessing datagridview as
int temp2noofrows = autoGridView.Rows.Count - 1;// here i dn't understand how to call my delegate method, so i can avoid cross threading access error
Try with Action Delegate
and assuming that you are using .net 2.0 and above
autoGridView.Invoke(
new Action(
delegate()
{
int temp2noofrows = autoGridView.Rows.Count - 1;//
}
)
);
The problem you are going to have with things like this is you will need a very specific update method for the delegate to run. For example updating the text in a textbox.
Create a delegate that has the same signature as a method that has been previously defined:
public delegate void UpdateTextCallback(string text);
In your thread, you can call the Invoke method on the TextBox, passing the delegate to call, as well as the parameters.
myTextBox.Invoke(new UpdateTextCallback(this.UpdateText),
new object[]{"Text generated on non-UI thread."});
And this is the actual method that will run your code.
// Updates the textbox text.
private void UpdateText(string text)
{
// Set the textbox text.
myTextBox.Text = text;
}
Note: Do not create a method that matches the EventHandler delegate signature and pass that. The implementation of Invoke on the Control class will not take into account the parameters passed to Invoke if the type of the delegate is EventHandler. It will pass the control that Invoke was called on for the sender parameter as well as the value returned by EventArgs.Empty for the e parameter.
So in your case you need to make sure you pass in all the information you need in order to update your grid.