C #에서 익명 메소드 구독 취소


222

이벤트에서 익명 메소드를 탈퇴 할 수 있습니까?

다음과 같은 이벤트에 가입하면

void MyMethod()
{
    Console.WriteLine("I did it!");
}

MyEvent += MyMethod;

다음과 같이 탈퇴 할 수 있습니다.

MyEvent -= MyMethod;

그러나 익명의 방법으로 구독하면 :

MyEvent += delegate(){Console.WriteLine("I did it!");};

이 익명 메소드를 탈퇴 할 수 있습니까? 그렇다면 어떻게?


4
이것을 할 수 없는지에 관해서는 : stackoverflow.com/a/25564492/23354
Marc Gravell

답변:


230
Action myDelegate = delegate(){Console.WriteLine("I did it!");};

MyEvent += myDelegate;


// .... later

MyEvent -= myDelegate;

주변 대표를 참조하십시오.


141

한 가지 기술은 익명 메소드를 보유 할 변수를 선언 한 다음 익명 메소드 자체에서 사용할 수 있습니다. 원하는 행동이 이벤트가 처리 된 후에 구독을 취소했기 때문에 이것은 나를 위해 일했습니다.

예:

MyEventHandler foo = null;
foo = delegate(object s, MyEventArgs ev)
    {
        Console.WriteLine("I did it!");
        MyEvent -= foo;
    };
MyEvent += foo;

1
Resharper는 이러한 종류의 코드를 사용하여 수정 된 클로저에 액세스하는 것에 대해 불평합니다. 익명 메소드의 본문에있는 'foo'변수가 실제로 익명 메소드 자체를 참조한다는 것을 확신합니까?
BladeWise

7
나는 내 빚에 대한 답을 찾았으며, 'foo'는 익명의 메소드 itslef에 대한 참조를 진정으로 보유 할 것입니다. 익명 메소드가 지정되기 전에 캡처되므로 캡처 된 변수가 수정됩니다.
BladeWise

2
그것이 바로 내가 필요한 것입니다! = null이 누락되었습니다. (MyEventHandler foo = 대리자 {... MyEvent- = foo;}; MyEvent + = foo; 작동하지 않습니다 ...)
TDaver

Resharper 6.1은 배열로 선언하면 불평하지 않습니다. 조금 이상해 보이지만이 툴에 대한 툴을 맹목적으로 믿어 보겠습니다. MyEventHandler [] foo = {null}; foo [0] = ... {... MyEvent-= foo [0]; }; MyEvent + = foo [0];
Mike Post

21

메모리에서 스펙은 익명 메소드로 작성된 대리자의 동등성과 관련하여 명시 적으로 동작을 보증하지 않습니다.

구독을 취소해야하는 경우 "일반"방법을 사용하거나 다른 곳에서 대리인을 유지하여 구독하는 데 사용한 것과 정확히 동일한 대리인으로 구독을 취소해야합니다.


존, 무슨 일이야? 이해가 안 돼요 "J c"에 노출 된 솔루션이 제대로 작동하지 않습니까?
Eric Ouellet

@EricOuellet :이 답변은 기본적으로 "대리인을 다른 곳에 유지하여 구독하는 데 사용한 것과 정확히 동일한 대리자를 구독 취소 할 수 있습니다"의 구현입니다.
Jon Skeet

Jon, 죄송합니다. 귀하의 답변이 무엇을 의미하는지 "J c"솔루션이 동일한 대리자를 사용하여 구독 및 구독 취소하지 않는 위치를 파악하려고 노력하지만 답변을 드릴 수는 없습니다. 아마도 당신은 당신이 말하는 것을 설명하는 기사에서 저를 가리킬 수 있습니까? 나는 당신의 평판에 대해 알고 있으며, 당신이 의미하는 바를 이해하고 싶습니다.
Eric Ouellet

1
발견 : msdn.microsoft.com/en-us/library/ms366768.aspx 그러나 익명을 사용하지 않는 것이 좋지만 큰 문제가 없다고 말하지 않습니까?
Eric Ouellet

나는 그것을 발견했다 ... 감사합니다 (Michael Blome 답변 참조) : social.msdn.microsoft.com/Forums/en-US/csharplanguage/thread/…
Eric Ouellet

16

3.0에서는 다음과 같이 단축 할 수 있습니다.

MyHandler myDelegate = ()=>Console.WriteLine("I did it!");
MyEvent += myDelegate;
...
MyEvent -= myDelegate;

15

이후 C # 7.0 지방 기능 기능이 해제되어, 접근 방식 제안 에 의해 J C는 정말 깔끔한된다.

void foo(object s, MyEventArgs ev)
{
    Console.WriteLine("I did it!");
    MyEvent -= foo;
};
MyEvent += foo;

솔직히 말하면 변수로 익명 함수가 없습니다. 그러나 귀하의 경우에 그것을 사용하려는 동기가 지역 기능에 적용될 수 있다고 생각합니다.


1
가독성을 높이기 위해 MyEvent + = foo; foo 선언 앞에 있어야합니다.
Mark Zhukovsky

9

델리게이트에 대한 참조를 유지하는 대신 이벤트의 호출 목록을 호출자에게 다시 제공하기 위해 클래스를 계측 할 수 있습니다. 기본적으로 다음과 같이 작성할 수 있습니다 (MyEvent가 MyClass 내에 선언되었다고 가정).

public class MyClass 
{
  public event EventHandler MyEvent;

  public IEnumerable<EventHandler> GetMyEventHandlers()  
  {  
      return from d in MyEvent.GetInvocationList()  
             select (EventHandler)d;  
  }  
}

따라서 MyClass 외부에서 전체 호출 목록에 액세스하고 원하는 핸들러를 구독 취소 할 수 있습니다. 예를 들어 :

myClass.MyEvent -= myClass.GetMyEventHandlers().Last();

이 기술에 대한 전체 게시물을 여기 에 작성했습니다 .


2
이것은 그들이 나를 구독 한 경우 실수로 이벤트에서 다른 인스턴스를 구독 취소 할 수 있음을 의미합니까?
dumbledad

@dumbledad는 물론 마지막으로 등록 된 것을 등록 취소합니다. 특정 익명 대리자를 동적으로 구독 취소하려면 어떻게 든 식별해야합니다. 다음 참조를 유지하는 것이 좋습니다 :)
LuckyLikey

꽤 멋지고, 당신이하고있는 일이지만 이것이 유용 할 수있는 한 가지 경우를 상상할 수 없습니다. 그러나 나는 OP의 질문을 실제로 해결하지는 않습니다. -> +1. IMHO는 나중에 익명 등록을 해제해야하는 경우 익명 위임을 사용해서는 안됩니다. 그것들을 유지하는 것은 바보입니다-> 더 나은 방법을 사용하십시오. 호출 목록에서 일부 대리인을 제거하는 것은 매우 임의적이며 쓸모가 없습니다. 틀 렸으면 말해줘. :)
LuckyLikey 2016 년

6

절름발이 접근 방식 :

public class SomeClass
{
  private readonly IList<Action> _eventList = new List<Action>();

  ...

  public event Action OnDoSomething
  {
    add {
      _eventList.Add(value);
    }
    remove {
      _eventList.Remove(value);
    }
  }
}
  1. 이벤트 추가 / 제거 메소드를 대체하십시오.
  2. 해당 이벤트 핸들러 목록을 유지하십시오.
  3. 필요한 경우 모두 지우고 다른 것을 다시 추가하십시오.

이것은 작동하지 않거나 가장 효율적인 방법 일 수 있지만 작업을 완료해야합니다.


14
절름발이라고 생각되면 게시하지 마십시오.
Jerry Nixon

2

탈퇴를 제어하려면 허용 된 답변에 표시된 경로로 이동해야합니다. 그러나 구독 클래스가 범위를 벗어날 때 참조를 지우는 것에 관심이 있다면 약한 참조를 사용하는 다른 (약간 복잡한) 솔루션이 있습니다. 방금 이 주제에 대한 질문과 답변 을 게시했습니다 .


2

하나의 간단한 해결책 :

eventhandle 변수를 매개 변수로 자신에게 전달하십시오. 멀티 스레딩으로 인해 원래 작성된 변수에 액세스 할 수없는 경우 이벤트를 사용할 수 있습니다.

MyEventHandler foo = null;
foo = (s, ev, mehi) => MyMethod(s, ev, foo);
MyEvent += foo;

void MyMethod(object s, MyEventArgs ev, MyEventHandler myEventHandlerInstance)
{
    MyEvent -= myEventHandlerInstance;
    Console.WriteLine("I did it!");
}

MyEvent가 실행되기 전에 두 번 호출되면 어떻게 MyEvent -= myEventHandlerInstance;됩니까? 가능하다면 오류가있을 것입니다. 그러나 그것이 사실인지 확실하지 않습니다.
LuckyLikey

0

이 대리자와 함께 일부 객체를 참조하려면 Delegate.CreateDelegate (Type, Object target, MethodInfo methodInfo) .net을 사용하여 대리자가 target 및 methodInfo로 동일한 것으로 간주하십시오


0

구독 된 eventHandler에 대한 참조를 유지하는 가장 좋은 방법은 사전을 사용하여 수행 할 수 있습니다.

이 예제에서는 익명의 메서드를 사용하여 DataGridViews 집합에 대해 mergeColumn 매개 변수를 포함시켜야합니다.

enable 매개 변수를 true로 설정하여 MergeColumn 메서드를 사용하면 이벤트를 false로 사용하면 이벤트가 비활성화됩니다.

static Dictionary<DataGridView, PaintEventHandler> subscriptions = new Dictionary<DataGridView, PaintEventHandler>();

public static void MergeColumns(this DataGridView dg, bool enable, params ColumnGroup[] mergedColumns) {

    if(enable) {
        subscriptions[dg] = (s, e) => Dg_Paint(s, e, mergedColumns);
        dg.Paint += subscriptions[dg];
    }
    else {
        if(subscriptions.ContainsKey(dg)) {
            dg.Paint -= subscriptions[dg];
            subscriptions.Remove(dg);
        }
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.