views:

158

answers:

1

I am getting all events, with a certain attribute, and I want to modify these events adding a call to another method.

var type = GetType();
var events = type.GetEvents().Where(e => e.GetCustomAttributes(typeof(ExecuteAttribute), false).Length > 0);

foreach (var e in events)
{
    var fi = type.GetField(e.Name, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetField);
    var d = (Delegate)fi.GetValue(this);
    var methods = d.GetInvocationList();
    foreach (var m in methods)
    {
        var args = e.EventHandlerType.GetMethod("Invoke").GetParameters().Select(p => Expression.Parameter(p.ParameterType, "p")).ToArray();
        var body = m.Method.GetMethodBody();

        /**

        TODO:

        Create a new method with the body of the previous
        and add a call to another method

        Remove current method
        Add the new created method

        **/
    }
}

What I want is basically what is commented above. "Modify" the subscribed methods of the event. I guess I can't subscribe to it, because I need to pass the parameters the method is passing to the big handler (the new method).

One more example based on this question. I want to convert this:

var e += (x) =>
{
    var y = x;
};

To something like this:

var e += (x) =>
{
    var y = x;

    BigHandler(x); // injected code
};

Or this:

var e += (x) => // new method
{
    previousE(x); // previous method

    BigHandler(x); // additional code
}

How can I do it?


The bigger goal:

I need to "detect" when an event is fired and call a method. I also need to send the parameters it is using.

So I can make something like:

public delegate void OnPostSaved(Post p);
[Execute]
public event OnPostSaved PostSaved;

public void Save()
{
    /* save stuff */
    // assume that there is already an event subscribed
    PostSaved(post);
}

Then on my handler method I can check if an event from XYZ was fired, check which event was fired, retrieve the parameter and do something. For example:

public void BigHandler(string eventName, params object[] p)
{
    if (eventName == "PostSaved")
    {
        var post = p[0] as Post;
        MessageBoard.Save("User posted on the blog: " + post.Content);
    }
}

I know it can be achieved using PostSharp, but I can't use it. I need another solution.


Related


Update 2010-09-27 I couldn't find a solution neither more info on it, I still need help. Added +150 bounty.

+4  A: 

If you're just adding a call, it's really easy - you don't need to fetch all the subscribed delegates or anything like that; just subscribe with reflection:

var type = GetType();
// Note the small change here to make the filter slightly simpler
var events = type.GetEvents()
                 .Where(e => e.IsDefined(typeof(ExecuteAttribute), false));

foreach (var e in events)
{
    // "handler" is the event handler you want to add
    e.AddEventHandler(this, handler);
}

Now, I've made an assumption about what you want to do - namely call your method once each time the event is raised. That's not the same as calling your method once every time an event handler is called which is what your original code would have done. That's much harder... but do you really need it?

(If you could give more information about the bigger goal here, it would really help.)

Note that the code I've provided here should work regardless of how the event is implemented - it doesn't rely on it being a field-like event.

EDIT: Okay, here's a complete example showing how you can add a plain EventHandler delegate to any event which matches the normal eventing pattern:

using System;
using System.Reflection;

class CustomEventArgs : EventArgs {}

delegate void CustomEventHandler(object sender, CustomEventArgs e);

class Publisher
{
    public event EventHandler PlainEvent;
    public event EventHandler<CustomEventArgs> GenericEvent;
    public event CustomEventHandler CustomEvent;

    public void RaiseEvents()
    {
        PlainEvent(this, new EventArgs());
        GenericEvent(this, new CustomEventArgs());
        CustomEvent(this, new CustomEventArgs());
    }
}

class Test
{
    static void Main()
    {
        Publisher p = new Publisher();

        Type type = typeof(Publisher);

        foreach (EventInfo eventInfo in type.GetEvents())
        {
            string name = eventInfo.Name;
            EventHandler handler = (s, args) => ReportEvent(name, s, args);
            // Make a delegate of exactly the right type
            Delegate realHandler = Delegate.CreateDelegate(
                 eventInfo.EventHandlerType, handler.Target, handler.Method);
            eventInfo.AddEventHandler(p, realHandler);
        }

        p.RaiseEvents();
    }

    static void ReportEvent(string name, object sender, EventArgs args)
    {
        Console.WriteLine("Event {0} name raised with args type {1}",
                          name, args.GetType());
    }
}

Note how we have to construct the exact type of delegate required - but we know we can do that from a compatible method, so long as everything follows the normal event pattern.

Jon Skeet
I've added the "bigger goal". Your current answer is close, sorry that I forgot to mention that I need to get the parameters the method subscribing is passing. I've added more info...
BrunoLM
@BrunoLM: That's just a matter of building the appropriate delegate, by the looks of it. I'm going out now, but I'll have a look in about an hour, if you haven't solved it by then. Lambda expressions may well be your friend.
Jon Skeet
@Jon Skeet: I still don't get it... How can I get the object that the event is using? The only way I can think of is to modify the same method. I need something like `BigHandler(eventName, params_from_the_event.ToArray<object>()`
BrunoLM
@BrunoLM: What do you mean by "the object that the event is using"? Do you mean the `(object sender, EventArgs e)` part (or whatever the signature of the event is)?
Jon Skeet
@Jon Skeet: Whatever signature. I want to get all params and throw into an `object[]`
BrunoLM
@Jon Skeet: I believe the signature is not a problem. The var `args` on my code return the signature of the method.
BrunoLM
@BrunoLM: Do you actually have to cope with *any* kind of event, or just a specific set of them? It would be easy if everything followed the convention of `(object sender, T args)` where T is some type deriving from `EventArgs`, for example.
Jon Skeet
@Jon Skeet: It would be better if it worked with any kind of event. But if there is a way to do it using `(object sender, T args)` it would be enough. I could change the events to match this signature...
BrunoLM
@BrunoLM: Okay, that should be doable - especially if T derives from EventArgs, again as per the pattern.
Jon Skeet
@BrunoLM: I've created a test program which you can use to see what to do.
Jon Skeet
@Jon Skeet: Wonderful. Thank you!!!
BrunoLM
@BrunoLM: Glad we got there in the end :)
Jon Skeet