Vra

Die samesteller verstik gewoonlik wanneer 'n gebeurtenis nie verskyn langs 'n += of 'n -=, so ek is nie seker of dit moontlik is.

Ek wil in staat wees om 'n gebeurtenis te identifiseer met behulp van 'n uitdrukking boom, so ek kan 'n gebeurtenis watcher vir 'n toets te skep. Die sintaksis sal soos volg lyk:

using(var foo = new EventWatcher(target, x => x.MyEventToWatch) {
    // act here
}   // throws on Dispose() if MyEventToWatch hasn't fired

My vrae is tweeledig:

  1. Sal die samesteller verstik? En indien wel, enige voorstelle oor hoe om dit te voorkom?
  2. Hoe kan ek ontleed die Expression voorwerp van die konstruktor ten einde te heg aan die MyEventToWatch geval van target?
Was dit nuttig?

Oplossing

Edit: As Curt het daarop gewys, is my implementering eerder gebrekkig deurdat dit net gebruik kan word vanuit die klas wat die geleentheid verklaar :) in plaas van "x => x.MyEvent" terugkeer van die gebeurtenis, dit was op pad terug van die steun in die veld, wat net accessble deur die klas.

Sedert uitdrukkings nie opdrag state, 'n aangepaste uitdrukking soos kan bevat "( x, h ) => x.MyEvent += h" kan nie gebruik word om die geleentheid by te haal, so besinning nodig sou wees om in plaas gebruik word. 'N Korrekte uitvoering nodig sou wees om nadenke te gebruik om die EventInfo vir die gebeurtenis (wat, Ongelukkig, sal nie sterk getik) te haal.

Andersins, die enigste updates wat gemaak moet word is om die weerspieël EventInfo stoor en gebruik die AddEventHandler / RemoveEventHandler metodes om die luisteraar te registreer (in plaas van die handleiding Delegate Combine / Remove oproepe en veld stelle). Die res van die implementering moet nie verander moet word. Sterkte:)


Nota: Dit is demonstrasie-gehalte-kode wat 'n hele paar aannames oor die formaat van die accessor maak. Behoorlike foutopsporing, hantering van statiese gebeure, ens, het oorgebly soos 'n oefening om die leser;)

public sealed class EventWatcher : IDisposable {
  private readonly object target_;
  private readonly string eventName_;
  private readonly FieldInfo eventField_;
  private readonly Delegate listener_;
  private bool eventWasRaised_;

  public static EventWatcher Create<T>( T target, Expression<Func<T,Delegate>> accessor ) {
    return new EventWatcher( target, accessor );
  }

  private EventWatcher( object target, LambdaExpression accessor ) {
    this.target_ = target;

    // Retrieve event definition from expression.
    var eventAccessor = accessor.Body as MemberExpression;
    this.eventField_ = eventAccessor.Member as FieldInfo;
    this.eventName_ = this.eventField_.Name;

    // Create our event listener and add it to the declaring object's event field.
    this.listener_ = CreateEventListenerDelegate( this.eventField_.FieldType );
    var currentEventList = this.eventField_.GetValue( this.target_ ) as Delegate;
    var newEventList = Delegate.Combine( currentEventList, this.listener_ );
    this.eventField_.SetValue( this.target_, newEventList );
  }

  public void SetEventWasRaised( ) {
    this.eventWasRaised_ = true;
  }

  private Delegate CreateEventListenerDelegate( Type eventType ) {
    // Create the event listener's body, setting the 'eventWasRaised_' field.
    var setMethod = typeof( EventWatcher ).GetMethod( "SetEventWasRaised" );
    var body = Expression.Call( Expression.Constant( this ), setMethod );

    // Get the event delegate's parameters from its 'Invoke' method.
    var invokeMethod = eventType.GetMethod( "Invoke" );
    var parameters = invokeMethod.GetParameters( )
        .Select( ( p ) => Expression.Parameter( p.ParameterType, p.Name ) );

    // Create the listener.
    var listener = Expression.Lambda( eventType, body, parameters );
    return listener.Compile( );
  }

  void IDisposable.Dispose( ) {
    // Remove the event listener.
    var currentEventList = this.eventField_.GetValue( this.target_ ) as Delegate;
    var newEventList = Delegate.Remove( currentEventList, this.listener_ );
    this.eventField_.SetValue( this.target_, newEventList );

    // Ensure event was raised.
    if( !this.eventWasRaised_ )
      throw new InvalidOperationException( "Event was not raised: " + this.eventName_ );
  }
}

Gebruik is effens anders as wat voorgestel, ten einde voordeel te trek uit tipe afleiding neem:

try {
  using( EventWatcher.Create( o, x => x.MyEvent ) ) {
    //o.RaiseEvent( );  // Uncomment for test to succeed.
  }
  Console.WriteLine( "Event raised successfully" );
}
catch( InvalidOperationException ex ) {
  Console.WriteLine( ex.Message );
}

Ander wenke

Ook ek wou om dit te doen, en ek het vorendag gekom met 'n pretty cool manier dat so iets Keiser XLII idee nie. Dit maak nie gebruik Expression bome egter soos genoem dit kan nie gedoen word as Expression bome nie die gebruik van += of -= toelaat.

Ons kan egter gebruik 'n netjiese truuk waar ons gebruik NET Remo Ting Proxy (of enige ander Proxy soos LinFu of Castle DP) om 'n oproep te Voeg onderskep / Verwyder hanteerder op 'n baie korte duur volmag voorwerp. Die rol van hierdie volmag doel is om eenvoudig 'n paar metode 'n beroep op dit, en om voorsiening te maak sy metode oproepe te onderskep, op watter punt kan ons uitvind die naam van die gebeurtenis.

Dit klink vreemd, maar hier is die kode (wat by the way net werk as jy 'n MarshalByRefObject of 'n koppelvlak vir die proxy voorwerp)

veronderstel ons het die volgende interface en klas

public interface ISomeClassWithEvent {
    event EventHandler<EventArgs> Changed;
}


public class SomeClassWithEvent : ISomeClassWithEvent {
    public event EventHandler<EventArgs> Changed;

    protected virtual void OnChanged(EventArgs e) {
        if (Changed != null)
            Changed(this, e);
    }
}

Dan kan ons 'n baie eenvoudig klas wat 'n Action<T> afgevaardigde wat sal kry geslaag sommige geval van T verwag het.

Hier is die kode

public class EventWatcher<T> {
    public void WatchEvent(Action<T> eventToWatch) {
        CustomProxy<T> proxy = new CustomProxy<T>(InvocationType.Event);
        T tester = (T) proxy.GetTransparentProxy();
        eventToWatch(tester);

        Console.WriteLine(string.Format("Event to watch = {0}", proxy.Invocations.First()));
    }
}

Die geheim is om die proxy voorwerp slaag om die Action<T> afgevaardigde verskaf.

Waar het ons die volgende CustomProxy<T> kode, wat die oproep onderskep om += en -= op die proxy voorwerp

public enum InvocationType { Event }

public class CustomProxy<T> : RealProxy {
    private List<string> invocations = new List<string>();
    private InvocationType invocationType;

    public CustomProxy(InvocationType invocationType) : base(typeof(T)) {
        this.invocations = new List<string>();
        this.invocationType = invocationType;
    }

    public List<string> Invocations {
        get { 
            return invocations; 
        }
    }

    [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.Infrastructure)]
    [DebuggerStepThrough]
    public override IMessage Invoke(IMessage msg) {
        String methodName = (String) msg.Properties["__MethodName"];
        Type[] parameterTypes = (Type[]) msg.Properties["__MethodSignature"];
        MethodBase method = typeof(T).GetMethod(methodName, parameterTypes);

        switch (invocationType) {
            case InvocationType.Event:
                invocations.Add(ReplaceAddRemovePrefixes(method.Name));
                break;
            // You could deal with other cases here if needed
        }

        IMethodCallMessage message = msg as IMethodCallMessage;
        Object response = null;
        ReturnMessage responseMessage = new ReturnMessage(response, null, 0, null, message);
        return responseMessage;
    }

    private string ReplaceAddRemovePrefixes(string method) {
        if (method.Contains("add_"))
            return method.Replace("add_","");
        if (method.Contains("remove_"))
            return method.Replace("remove_","");
        return method;
    }
}

En dan ons al wat oorbly is om dit te gebruik soos volg

class Program {
    static void Main(string[] args) {
        EventWatcher<ISomeClassWithEvent> eventWatcher = new EventWatcher<ISomeClassWithEvent>();
        eventWatcher.WatchEvent(x => x.Changed += null);
        eventWatcher.WatchEvent(x => x.Changed -= null);
        Console.ReadLine();
    }
}

Deur dit te doen Ek sal dit uitset sien:

Event to watch = Changed
Event to watch = Changed

'n NET geval is nie eintlik 'n voorwerp, is dit 'n eindpunt verteenwoordig deur twee funksies - een vir die toevoeging van een en vir die verwydering van 'n hanteerder. Dis hoekom die samesteller sal nie toelaat dat jy enigiets anders as doen + = (wat dié byvoeging verteenwoordig) of -. = (Wat die verwyder verteenwoordig)

Die enigste manier om te verwys na 'n gebeurtenis vir metaprogramming doeleindes is as 'n System.Reflection.EventInfo, en besinning is waarskynlik die beste manier (indien nie die enigste manier) Ahold van een te kry.

EDIT: Keiser XLII het 'n paar pragtige kode wat moet werk vir jou eie gebeure geskryf, op voorwaarde dat jy hulle het te kenne gegee van C # eenvoudig as

public event DelegateType EventName;

Dit is omdat C # skep twee dinge vir jou uit daardie verklaring:

  1. 'n private afgevaardigde veld te dien as die agtergrond stoor vir die gebeurtenis
  2. Die werklike gebeurtenis saam met implementering kode wat gebruik maak van die afgevaardigde.

gerieflik, beide van hulle het dieselfde naam. Dis hoekom die voorbeeld kode sal werk vir jou eie gebeure.

Jy kan egter nie staatmaak op dit die geval wees wanneer die gebruik van gebeure geïmplementeer deur ander biblioteke. In die besonder, moenie die gebeure in Windows Vorms en in WPF nie hul eie steun stoor, sodat die monster-kode sal nie werk vir hulle.

Hoewel Keiser XLII reeds die antwoord vir hierdie het, het ek gedink dit was die moeite werd om my herskryf van hierdie deel. Ongelukkig is geen vermoë om die Event via Expression Tree kry, ek gebruik die naam van die gebeurtenis.

public sealed class EventWatcher : IDisposable {
     private readonly object _target;
     private readonly EventInfo _eventInfo;
     private readonly Delegate _listener;
     private bool _eventWasRaised;

     public static EventWatcher Create<T>(T target, string eventName) {
         EventInfo eventInfo = typeof(T).GetEvent(eventName);
         if (eventInfo == null)
            throw new ArgumentException("Event was not found.", eventName);
         return new EventWatcher(target, eventInfo);
     }

     private EventWatcher(object target, EventInfo eventInfo) {
         _target = target;
         _eventInfo = event;
         _listener = CreateEventDelegateForType(_eventInfo.EventHandlerType);
         _eventInfo.AddEventHandler(_target, _listener);
     }

     // SetEventWasRaised()
     // CreateEventDelegateForType

     void IDisposable.Dispose() {
         _eventInfo.RemoveEventHandler(_target, _listener);
         if (!_eventWasRaised)
            throw new InvalidOperationException("event was not raised.");
     }
}

En gebruik is:

using(EventWatcher.Create(o, "MyEvent")) {
    o.RaiseEvent();
}
Gelisensieer onder: CC-BY-SA met toeskrywing
Nie verbonde aan StackOverflow
scroll top