2012-06-20 4 views
3

개발자가 친숙한 + = 구. 및 인텔리 센스를 계속 사용할 수있게하면서 느슨한 결합을 지원하는 이벤트 브로커를 작성하려고합니다. DynamicInvoke를 사용하지 않고 대표를 총괄적으로 호출하는 방법에 어려움을 겪고 있습니다.일] 적으로 이벤트 브로커의 위임자 저장 W 호출

일반 아이디어 :

  • 모든 이벤트는 인터페이스에 정의 된 각 이벤트는 EventInfoBase에서 파생 한 인수를합니다. 가입자 측에서

    public delegate void EventDelegate<in T>(T eventInfo) where T : EventInfoBase; 
    
  • , 클라이언트는 이벤트 인터페이스를 구현하는 예를 들어 윈저 컨테이너를 묻는다. 윈저는 모든 + = (add_xxx) 및 - = (remove_xxx) 호출을 차단하는 프록시를 반환합니다. 이벤트가 발생하면 향후 조회 및 실행을 위해 형식 및 위임 정보가 저장됩니다. 현재 대리자는 대리자로 저장되지만 EventDelegate와 같은 형식으로 저장하는 것이 좋습니다.

  • 게시자 측에서 게시자는 이벤트 인터페이스의 소스로 이벤트 브로커에 등록합니다. 리플렉션을 통해 이벤트 브로커는 EventDelegate 유형의 대리자 인 각 이벤트를 구독합니다. <EventInfoBase>.

  • 이벤트가 발생하면 이벤트 브로커는 적절한 대리인을 찾아 실행합니다.

문제 : EventInfoBase 기본 클래스와 게시자에 이벤트 핸들러를 추가

는 contravariance의 법적 사용하는 것입니다. 그러나 이벤트 브로커는 클라이언트 구독을 EventDelegate <EventInfoBase>으로 저장할 수 없습니다. Eric Lippert가이 blog post에서 이유를 설명합니다.

DynamicInvoke를 사용하지 않고 나중에 클라이언트 구독 (델리게이트)을 저장할 수있는 방법에 대한 아이디어가 있습니까?

추가 세부 사항을 업데이트 :

구독자 이벤트 인터페이스의 이벤트 브로커를 요청하고 필요에 따라 다음 이벤트에 가입.

// a simple event interface 
public class EventOneArgs : EventInfoBase { } 
public class EventTwoArgs : EventInfoBase { } 
public interface ISomeEvents 
{ 
    event EventDelegate<EventOneArgs> EventOne; 
    event EventDelegate<EventTwoArgs> EventTwo; 
} 

// client gets the event broker and requests the interface 
// to the client it looks like a typical object with intellisense available 
IWindsorContainer cont = BuildContainer(); 
var eb = cont.Resolve<IEventBroker>(); 
var service = eb.Request<ISomeEvents>(); 
service.EventOne += new EventDelegate<EventOneArgs>(service_EventOne); 
service.EventTwo += new EventDelegate<EventTwoArgs>(service_EventTwo); 

이벤트 중개자는 이벤트 인터페이스에 대해 아무것도 모르고 그 인터페이스에 대한 프록시를 리턴합니다. 모든 + = 통화가 도청되고 구독자가 대리인 사전을 추가했습니다.

public T Request<T>(string name = null) where T : class 
{ 
    ProxyGenerator proxygenerator = new ProxyGenerator(); 
    return proxygenerator.CreateInterfaceProxyWithoutTarget(typeof(T), 
      new EventSubscriptionInterceptor(this, name)) as T; 
} 

public void Intercept(IInvocation invocation) 
{ 
    if (invocation.Method.IsSpecialName) 
    { 
    if (invocation.Method.Name.Substring(0, s_SubscribePrefix.Length) == s_SubscribePrefix) // "add_" 
    { 
     // DeclaringType.FullName will be the interface type 
     // Combined with the Name - prefix, it will uniquely define the event in the interface 
     string uniqueName = invocation.Method.DeclaringType.FullName + "." + invocation.Method.Name.Substring(s_SubscribePrefix.Length); 
     var @delegate = invocation.Arguments[0] as Delegate; 
     SubscirptionMgr.Subscribe(uniqueName, @delegate); 
     return; 
    } 
    // ... 
    } 
} 

SubscriptionManager 저장 대리인

는 형인 T EventDelegate 이벤트에 의해 정의되는 유도 유형 <T>.

게시자 : 게시자는 이벤트 인터페이스의 원본으로 등록합니다. 목표는 명시 적으로 이벤트 브로커를 호출하고 EventName (args)의 일반적인 C# 구문을 허용하지 않아도되도록하는 것이 었습니다.

public class SomeEventsImpl : ISomeEvents 
{ 
    #region ISomeEvents Members 
    public event Ase.EventBroker.EventDelegate<EventOneArgs> EventOne; 
    public event Ase.EventBroker.EventDelegate<EventTwoArgs> EventTwo; 
    #endregion 

    public SomeEventsImpl(Ase.EventBroker.IEventBroker eventBroker) 
    { 
     // register as a source of events 
     eventBroker.RegisterSource<ISomeEvents, SomeEventsImpl>(this); 
    } 

    public void Fire_EventOne() 
    { 
     if (EventOne != null) 
     { 
     EventOne(new EventOneArgs()); 
     } 
    } 
} 

이벤트 브로커는 공통의 핸들러와의 인터페이스에서 모든 이벤트 (AddEventHandler)에 가입 반사를 사용합니다.아직 핸들러 결합을 시도하지 않았습니다. 유형과 같이 이벤트가 발생할 때 사용할 수있는 추가 정보가 필요할 경우에 대비해 래퍼 클래스를 만들었습니다.

public void RegisterSource<T, U>(U instance) 
    where T : class 
    where U : class 
{ 
    T instanceAsEvents = instance as T; 
    string eventInterfaceName = typeof(T).FullName; 
    foreach (var eventInfo in instanceAsEvents.GetType().GetEvents()) 
    { 
     var wrapper = new PublishedEventWrapper(this, eventInterfaceName + "." + eventInfo.Name); 
     eventInfo.AddEventHandler(instance, wrapper.EventHandler); 
    } 
} 

class PublishedEventWrapper 
{ 
    private IEventPublisher m_publisher = null; 
    private readonly EventDelegate<EventInfoBase> m_handler; 

    private void EventInfoBaseHandler(EventInfoBase args) 
    { 
     if (m_publisher != null) 
     { 
     m_publisher.Publish(this, args); 
     } 
    } 

    public PublishedEventWrapper(IEventPublisher publisher, string eventName) 
    { 
     m_publisher = publisher; 
     EventName = eventName; 
     m_handler = new EventDelegate<EventInfoBase>(EventInfoBaseHandler); 
    } 

    public string EventName { get; private set; } 
    public EventDelegate<EventInfoBase> EventHandler 
    { 
     get { return m_handler; } 
    } 
} 

나는 게시에 대한 거짓말으로 인해 어려움을 겪고 있습니다. Publish 메서드는 이벤트에 대한 대리자를 조회하고이를 실행해야합니다. DynamicInvoke의 성능 문제로 인해 위임자를 올바른 EventDelegate <T> 양식으로 캐스팅하고 직접 호출했지만 수행 방법을 찾지 못했습니다.

나는 확실히 이것을 알아 내려고 노력했지만 시간이 없습니다. 아마 뭔가 간단한 것이 빠져 있습니다. 내가 본질적으로 보이는 다른 하나 (동적으로 생성 된) 위임을 래핑 시도했다 :

private static void WrapDelegate(Delegate d, DerivedInfo args) 
{ 
    var t = d as EventDelegate<DerivedInfo>; 
    if (t != null) 
    { 
     t(args); 
    } 
} 

어떤지도 바랍니다.

답변

2

DynamicInvoke를 사용하지 않고 나중에 호출 할 수 있도록 클라이언트 구독 (대리인)을 저장할 수있는 방법에 대한 아이디어가 있습니까? 당신이 내용을 자신을 관리하고 있기 때문에

public void Subscribe<T>(EventDelegate<T> handler) where T : EventInfoBase 
{ 
    Delegate existingHandlerPlain; 
    // We don't actually care about the return value here... 
    dictionary.TryGetValue(typeof(T), out existingHandlerPlain); 
    EventDelegate<T> existingHandler = (EventDelegate<T>) existingHandlerPlain; 
    EventDelegate<T> newHandler = existingHandler + handler; 
    dictionary[typeof(T)] = newHandler; 
} 

public void Publish<T>(EventInfo<T> info) where T : EventInfoBase 
{ 
    Delegate handlerPlain; 
    if (dictionary.TryGetValue(typeof(T), out handlerPlain)) 
    { 
     EventDelegate<T> handler = (EventDelegate<T>) handlerPlain; 
     handler(info); 
    } 
} 

캐스트는 항상 안전합니다 :

당신은 적절하게 캐스팅 한 후 Dictionary<Type, Delegate>을 사용하고 있었다.

사실상 다른 유형의 이벤트 핸들러를 결합하려고 시도하지만 여전히 분산 문제가 발생할 수 있습니다. 그게 문제라면 "정상적인"결합 작업을 사용하는 대신 명시 적으로 List<EventHandler<T>>을 사용해야합니다. 대리인을 포장에

+0

답장을 보내 주셔서 감사합니다. 델리게이트 컬렉션은 내가 사용하고있는 것입니다. 그러나 내가 취하고있는 접근 방식 때문에 퍼블리시 할 때 형식 매개 변수에 액세스 할 수 없습니다. void publish (delType 유형, Delegate del, EventInfoBase 정보) –

+0

@TedSchnackertz : 글쎄, 그렇게해야합니까? 'Publish' 메쏘드를 generic으로 만들 수 없습니까? * 모든 메소드를 generic으로 유지하지만, typeof (T)에 의해 * storage *를 사용하십시오. –

+0

나는 내가 뭘하려고하는지에 대한 세부 사항으로 질문을 갱신 할 것이다. –

1

해결 방법 :

나는 알려진 유형의 위임과 일반 대의원을 포장 할 수있는 방법을 찾기 위해 관리 않았다. 이것에 의해, SomeDelegate (args)의 표준 호출 규칙을 사용할 수가 있습니다. 이 솔루션에 의해 정의 된 일반적인 위임에 취

public delegate void EventDelegate<in T>(T eventInfo) where T : EventInfoBase; 

일반 대리자 인프라 코드를 사용하여 호출 할 수 있습니다 알려진 서명 대리인에 의해 싸여있다. 아직이 접근법의 성능을 확인하지 못했습니다. MethodInfo.Invoke를 호출하는 비용은 이벤트 구독으로 발생합니다. 이벤트를 실행하는 데 드는 비용은 DelegateWrapper의 코드입니다.

private static EventDelegate<EventInfoBase> DelegateWrapper<T>(Delegate @delegate) where T : EventInfoBase 
    { 
    return (o => 
     { 
      var t = @delegate as EventDelegate<T>; 
      var args = o as T; 
      if (t != null && o != null) 
      { 
       t(args); 
      } 
     } 
    ); 
    } 

    private static readonly MethodInfo s_eventMethodInfo = typeof(EventSubscriptionInterceptor).GetMethod("DelegateWrapper", BindingFlags.NonPublic | BindingFlags.Static); 

    private EventDelegate<EventInfoBase> GenerateDelegate(Delegate d) 
    { 
    MethodInfo closedMethod = s_eventMethodInfo.MakeGenericMethod(d.Method.GetParameters()[0].ParameterType); 
    var newDel = closedMethod.Invoke(null, new object[] { d }) as EventDelegate<EventInfoBase>; 
    return newDel; 
    } 

최소한이 코드는 '대부분'C# 구문을 사용하는 느슨하게 연결된 이벤트 브로커의 작동 프로토 타입을 제공합니다.

+0

minor comment :'@ delegate'에서'@'를 사용하는 것은 합법적이지만, 나는 'handler' 또는'eventHandler'로 개인적으로 바꿀 것입니다. – IAbstract