2010-12-03 3 views
1

J'écris une bibliothèque de classes pour une API Web.Comment faire pour élever des événements de classe parent avec Expression (C#)

J'ai une classe de base et une interface avec 30 blocs comme ceci:

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

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

    // more... 
} 

La classe de base appelée BaseHttpClient contient la mise en œuvre et toutes les méthodes sont vides et virtuels. Parce que l'API est assez non standard, j'hérite la classe de base avec une classe XmlClient. Cette classe remplace les méthodes virtuelles et fait le travail.

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 -= 
    } 
} 

Le problème est que je ne peux pas lever les événements:

L'événement 'BaseHttpClient.LogoffEnded' ne peut apparaître sur le côté gauche de + = ou - =

Une solution de base est de créer des méthodes dans la classe de base comme

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

Mais il y a aussi ma ny méthodes à créer. Je voudrais faire quelque chose comme:

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

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

Je suppose que cela concerne la réflexion et les expressions.

  • Est-ce une bonne façon de faire? (performace)
  • Quelle documentation pourrais-je lire pour faire ceci?
  • pourriez-vous me montrer un code valide pour cela?
+1

Vous ne savez pas si vous en êtes conscient ou non, mais vous pouvez rendre BaseHttpClient abstrait et déclarer le résumé des membres ISomethingApi au lieu de fournir une implémentation qui lève NotImplementedException. Si vous voulez vraiment lancer une exception, vous devriez envisager d'utiliser NotSupportedException car NotImplementedException suggère qu'il était censé avoir été implémenté par la classe dérivée, auquel cas il aurait dû être abstrait. – Tergiver

Répondre

1

Vous pouvez utiliser System.ComponentModel.EventHandlerList qui vous net deux avantages:

1) Vous aurez votre mécanisme de FireEvent .

2) Le membre Events n'utilise pas de mémoire sauf si des délégués sont inscrits. Si vous avez une classe avec 30 événements, vous avez 30 pointeurs dans l'empreinte de votre classe, qu'il y ait ou non des abonnés. EventHandlerList est un objet unique qui contient tous les délégués inscrits. C'est une carte très légère (pas un dictionnaire).Notez que les clés d'événement sont des objets statiques afin de ne pas être ajoutées à l'empreinte de la classe.

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

Ajouté:

Vous pouvez vous épargner typeing dans BaseHttpClient par writing a code snippet.

+0

Cela ne semble pas exister dans Silverlight/WP7 (Microsoft serious?). C'est vraiment étrange parce que c'est à partir de .NET 1.1! Je voudrais l'utiliser mais j'ai besoin de temps pour l'écrire pour Silverlight. Bonne réponse au passage. – SandRock

+0

La classe EventHandlerList est très simple, vous pouvez l'attraper avec Reflector. – Tergiver

+0

J'accepte cette réponse même si elle ne correspond pas à mon problème (Silverlight). Maintenant, je suis convaincu ** que les événements sont faux pour les opérations asynchrones **. J'utilise maintenant des méthodes avec callback en tant que paramètre et méthodes utilisant le pattern async/await. – SandRock

2

Vous pouvez utiliser deux méthodes d'extension statiques:

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

lequel vous pourriez faire:

public class MyClass 
{ 
    public event EventHandler MyEvent; 

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

Alors que vous pouvez en effet utiliser une expression, par exemple:

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

Vous voulez probablement supprimer l'expression redondante, et ju st utilisez un Func<T> à la place, car vous augmentez l'événement directement depuis la classe. A travers des expressions, vous devez compiler l'expression, alors que Func<T> ne pas:

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

Salut. Je suppose que le premier ne fonctionne que si vous avez une déclaration d'événement complète (champ EventHandler + propriété d'événement). Ce n'est pas ce que j'ai. – SandRock

+0

J'ai ensuite essayé d'utiliser la chose d'expression (qui compile), mais l'invocation ne compilera pas: classe XmlClient { public void GetMemberAsync (nom d'utilisateur string) { Raise (() => base.GetMemberEnded, nouvelles AsyncResponseArgs ()); }} > L'événement « BaseHttpClient.GetMemberEnded » ne peut apparaître sur le côté gauche de + = ou - = utilisant peut-être une expression plus complexe fera l'affaire. – SandRock

+0

Je viens d'essayer 'Raise (Expression >> expr, T eventArgs)' avec 'Raise ((x) => x.GetMemberEnded, nouveau AsyncResponseArgs ());'. Mais toujours la même erreur. – SandRock

1

Vous devez déplacer vos méthodes RaiseXXX vers la classe parente, où vos événements sont définis. Assurez-vous que ces méthodes sont au moins protégées.

Et n'oubliez pas d'appeler vos événements via une variable locale pour minimiser le champ d'erreur.

var e = MyEvent;

si (e! = Null) e (this, EventArgs.Empty);

+0

Le conseil semble juste mais cela ne répond pas à la question. Avez-vous de la documentation à ce sujet étant mieux? – SandRock

0

Vous pouvez ajouter une méthode à la classe de base qui prend le nom de l'événement en tant que chaîne et déclenche l'événement correspondant par réflexion comme

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

Je ne sais rien au sujet de la performance, cependant.

Questions connexes