开发者

How to raise parent class events with Expression (C#)

开发者 https://www.devze.com 2023-01-28 12:35 出处:网络
I\'m writing a class library for a Web API. I have a base class and an interface with 30 blocks like this:

I'm writing a class library for a Web API.

I have a base class and an interface with 30 blocks like this:

interface ISomethingApi {
    void AuthenticateAsync(string username, string password);
    event AsyncResponseHandler AuthenticateEnded;

    void GetMemberAsync(string username);
    event AsyncResponseHandler<Member> GetMemberEnded;

    // more...
}

The base class called BaseHttpClient contains the implementation and all methods are empty and virtual.

class BaseHttpClient : ISomethingApi {

    public virtual void GetMemberAsync(string username) {
        throw new NotImplementedException();
    }

    public event AsyncResponseHandler<Member> GetMemberEnded;

    // more...

}

Because the API is pretty non-standard, I am inheriting the base class with a XmlClient class. This class overrides virtual methods and do the job.

class XmlClient : BaseHttpClient {
    public override void GetMemberAsync(string username) {
        Member member;
        // process here

        // raising the event
        GetMemberEnded(this, new AsyncResponseArgs<Member>(member));
        // error: LogoffEnded can only appear on the left hand side of += or -=
    }
}

The problem is开发者_如何学编程 I can't raise the events:

The event 'BaseHttpClient.LogoffEnded' can only appear on the left hand side of += or -=

A basic solution is to create methods in the base class like

protected void RaiseLogoffEnded(AsyncResponseArgs args) {
    if (LogoffEnded != null) {
        LogoffEnded(this, args);
    }
}

But there are too many methods to create. I'd like to do something like:

public override void GetMemberAsync(string username) {
    Member member;
    // work done here

    RaiseEvent(x => x.GetMemberEnded, new AsyncResponseArgs<Member>(member));
}

I suppose this is about reflection and expressions.

  • Is it a right way to do? (performace)
  • What documentation could I read to make this?
  • could you show me a valid code for this?


You could use a couple of static extension methods:

static class Extensions
{
    public static void Raise(this EventHandler @event, object sender, EventArgs e)
    {
        if (@event != null)
            @event(sender, e);
    }

    public static void Raise<T>(this EventHandler<T> @event, object sender, T e) where T : EventArgs
    {
        if (@event != null)
            @event(sender, e);
    }
}

Whereby you could do:

public class MyClass
{
    public event EventHandler MyEvent;

    public void DoSomething()
    {
        MyEvent.Raise(this, EventArgs.Empty);   
    }
}

While you can in fact use an expression, e.g.:

public void Raise<T>(Expression<Func<EventHandler<T>>> expr, T eventArgs)
    where T : EventArgs
{
    EventHandler<T> handler = expr.Compile().Invoke();
    handler(this, eventArgs);
}

You probably want to do away with the redundant expression, and just use a Func<T> instead, as you are raising the event from the class directly. Through expressions, you would need to compile the expression, whereas Func<T> you don't:

public void Raise<T>(Func<EventHandler<T>> func, T eventArgs)
    where T : EventArgs
{
    EventHandler<T> handler = func();
    handler(this, eventArgs);
}


You can make use of System.ComponentModel.EventHandlerList which will net you two advantages:

1) You will have your FireEvent mechanism.

2) The Events member doesn't use memory unless there are delegates subscribed. If you have a class with 30 events, you have 30 pointers in your class' footprint, whether or not there are any subscribers. EventHandlerList is a single object that contains any and all delegates subscribed. It's a very light-weight map (not a Dictionary). Notice that the event keys are static objects so as not to add to the class' footprint.

class AsyncResponseArgs : EventArgs
{
    public Member Member { get; private set; }
    public AsyncResponseArgs(Member m)
    {
        Member = m;
    }
}

interface ISomethingApi
{
    void AuthenticateAsync(string username, string password);
    event EventHandler<AsyncResponseArgs> AuthenticateEnded;

    void GetMemberAsync(string username);
    event EventHandler<AsyncResponseArgs> GetMemberEnded;
}

class BaseHttpClient : ISomethingApi
{
    private EventHandlerList Events = new EventHandlerList();

    public virtual void AuthenticateAsync(string username, string password)
    {
        throw new NotImplementedException();
    }
    protected static object AuthenticateEndedEvent = new object();
    public event EventHandler<AsyncResponseArgs> AuthenticateEnded
    {
        add { Events.AddHandler(AuthenticateEndedEvent, value); }
        remove { Events.RemoveHandler(AuthenticateEndedEvent, value); }
    }

    public virtual void GetMemberAsync(string username)
    {
        throw new NotImplementedException();
    }
    protected static object GetMemberEndedEvent = new object();
    public event EventHandler<AsyncResponseArgs> GetMemberEnded
    {
        add { Events.AddHandler(GetMemberEndedEvent, value); }
        remove { Events.RemoveHandler(GetMemberEndedEvent, value); }
    }

    protected void FireEvent(object key, AsyncResponseArgs e)
    {
        EventHandler<AsyncResponseArgs> handler = (EventHandler<AsyncResponseArgs>)Events[key];
        if (handler != null)
            handler(this, e);
    }
}

class XmlClient : BaseHttpClient
{
    public override void GetMemberAsync(string username)
    {
        Member member;
        // process here 
        FireEvent(GetMemberEndedEvent, new AsyncResponseArgs(member));
    }
}

Added:

You can save yourself some typeing in BaseHttpClient by writing a code snippet.


You have to move your RaiseXXX methods to parent class, where you have your events defined. Make sure these methods are at least protected.

And don't forget to call your events via local variable to minimize error field.

var e = MyEvent;

if (e != null) e(this, EventArgs.Empty);


You could add a method to the base class that takes the event name as a String and raises the corresponding event via reflection like

public void Raise(String eventName, object source, EventArgs eventArgs)
{
    var field = this.GetType().GetField(eventName, BindingFlags.Instance | BindingFlags.NonPublic);
    if (field == null)
        throw new ArgumentException("No such event: " + eventName);

    var eventDelegate = (MulticastDelegate)field.GetValue(this);
    if (eventDelegate != null)
        foreach (var handler in eventDelegate.GetInvocationList())
            handler.Method.Invoke(handler.Target, new object[] { source, eventArgs });                
}

I don't know anything about performance, though.

0

精彩评论

暂无评论...
验证码 换一张
取 消