2011-04-11 5 views
11

나는 이것이 가능하지 않다고 확신하지만, 그럼에도 불구하고 요청할 것입니다. 이벤트에 싱글 - 샷 가입을하기 위해서는, 내가 자주 자신이 (자기 발명) 패턴을 사용하여 찾을에서싱글 샷 이벤트 구독

:

EventHandler handler=null; 
handler = (sender, e) => 
{ 
    SomeEvent -= handler; 
    Initialize(); 
}; 
SomeEvent += handler; 

그것은 보일러 플레이트의 꽤 많은, 그리고 그것은 또한 ReSharper에서한다 수정 된 클로저에 대해 우는 소리. 이 패턴을 확장 메소드 또는 유사하게 바꾸는 방법이 있습니까? 그것을하는 더 좋은 방법? 당신은 C#에서 이벤트에 참조 할 수있는 유일한 방법은에 (+=)에 가입하여 있기 때문에,

SomeEvent.OneShot(handler) 
+0

상용구 코드를 확장 메서드에 넣을 수없는 이유가 없습니다. 아니면 질문이 없었나요? – Achim

+0

구독 취소를 위해 이벤트를 어떻게 전달합니까? 공통 기본 유형이 있습니까? 대리자가 값 유형이 아닙니까 (즉, 어떻게 든 이벤트를 전달하면 사본을 처리하게됩니다) – spender

+0

@spender : 모든 대리자는 참조 유형입니다. –

답변

4

그것은 확장 방법에 리팩토링하는 것은 매우 쉬운 일이 아니다 :

적으로는, 내가 좋아하는 뭔가를하고 싶습니다 또는 수신 거부 (-=) (현재 클래스에서 선언 된 경우 제외).

Reactive Extensions에서와 동일한 접근 방식을 사용할 수 있습니다. Observable.FromEvent은 두 명의 대리인이 수신 거부 이벤트에 가입하는 데 사용됩니다. 그래서 당신은 그런 것을 할 수 있습니다 :

public static class EventHelper 
{ 
    public static void SubscribeOneShot(
     Action<EventHandler> subscribe, 
     Action<EventHandler> unsubscribe, 
     EventHandler handler) 
    { 
     EventHandler actualHandler = null; 
     actualHandler = (sender, e) => 
     { 
      unsubscribe(actualHandler); 
      handler(sender, e); 
     }; 
     subscribe(actualHandler); 
    } 
} 

... 

Foo f = new Foo(); 
EventHelper.SubscribeOneShot(
    handler => f.Bar += handler, 
    handler => f.Bar -= handler, 
    (sender, e) => { /* whatever */ }); 
+0

좋은 시도지만, 가입/탈퇴의 모든 위임과 관련하여 그것은 대략 같은 양의 상용구이지만, 처음 읽을 때는 분명하지 않은 익숙하지 않은 인터페이스를 사용합니다.또한 사용자가 제공 한 대리인의 가입/해지 시행이 없으므로 부서지기 쉬울 것이며 메서드의 이름에 신경을 씁니다. 고마워,하지만 난 내 fttb와 막대기 :) – spender

+0

나는 이것이 올바른 방향으로 단계라고 말하고 싶습니다. 결국, 코드의 의도는 메소드 이름에 의해 명확하게 기술되며 입력하기위한 코드가 약간 적습니다. 확실히 시작입니다. –

+1

+1, 어쨌든 호출자로부터'f.Bar + =/- = handler'를 추상화 할 수 없으며, 핸들러 대신 확장 메소드에서 수행되는 가입/제거는 버그가 아닌 기능으로 간주 될 수 있습니다.) –

1

다음 코드는 저에게 효과적입니다. 문자열을 통해 이벤트를 지정해야하는 것은 완벽하지 않지만이를 해결하는 방법은 없습니다. 현재 C# 버전에서는 불가능하다고 생각합니다.

using System; 
using System.Reflection; 

namespace TestProject 
{ 
    public delegate void MyEventHandler(object sender, EventArgs e); 

    public class MyClass 
    { 
     public event MyEventHandler MyEvent; 

     public void TriggerMyEvent() 
     { 
      if (MyEvent != null) 
      { 
       MyEvent(null, null); 
      } 
      else 
      { 
       Console.WriteLine("No event handler registered."); 
      } 
     } 
    } 

    public static class MyExt 
    { 
     public static void OneShot<TA>(this TA instance, string eventName, MyEventHandler handler) 
     { 
      EventInfo i = typeof (TA).GetEvent(eventName); 
      MyEventHandler newHandler = null; 
      newHandler = (sender, e) => 
          { 
           handler(sender, e); 
           i.RemoveEventHandler(instance, newHandler); 
          }; 
      i.AddEventHandler(instance, newHandler); 
     } 
    } 

    public class Program 
    { 
     static void Main(string[] args) 
     { 
      MyClass c = new MyClass(); 
      c.OneShot("MyEvent",(sender,e) => Console.WriteLine("Handler executed.")); 
      c.TriggerMyEvent(); 
      c.TriggerMyEvent(); 
     } 
    } 
} 
+0

사용자 지정 이벤트 접근자가 옵션 일 수도 있다는 것을 알았습니다. 하지만 그것은 귀하의 선호도와 요구 사항에 달려 있습니다. – Achim

1

난 당신이 호출 목록에 액세스 할 수 있도록 "사용자 정의"이벤트를 사용하는 것이 좋습니다 다음 동시에 읽고 호출 목록을 지우려면 Interlocked.Exchange를 사용하여 이벤트를 발생시킬 것이다. 원할 경우 간단한 링크 된 목록 스택을 사용하여 이벤트 수신/가입 취소/발생을 스레드로부터 안전하게 수행 할 수 있습니다. 이벤트가 발생하면 코드가 Interlocked.Exchange 뒤에 스택 항목의 순서를 반대로 할 수 있습니다. unsubscribe 메소드의 경우 호출 목록 항목 내에 플래그를 설정하는 것이 좋습니다. 이것은 이벤트가 발생하지 않고 반복적으로 구독 및 구독 취소 된 경우 이론적으로 메모리 누수가 발생할 수 있지만 아주 쉬운 스레드 안전 수신 거부 방법이 될 수 있습니다. 메모리 누수를 피하려면 목록에 등록되지 않은 이벤트의 수를 유지해야합니다. 너무 많은 구독 취소 된 이벤트가 목록에있는 경우 새 목록을 추가하려고 시도하면 add 메소드가 목록을 통과하여 제거 될 수 있습니다. 여전히 완전히 lock-free thread-safe 코드에서 작동 가능하지만 더 복잡합니다.

관련 문제