C # 'da anonim yöntemden çıkma


222

Anonim bir yöntemi bir etkinlikten iptal etmek mümkün mü?

Böyle bir etkinliğe abone olursam:

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

MyEvent += MyMethod;

Bunun gibi abone olabilirim:

MyEvent -= MyMethod;

Ama anonim bir yöntem kullanarak abone olursam:

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

Bu anonim yöntemin aboneliğini iptal etmek mümkün mü? Öyleyse nasıl?


4
Gelince neden : Bunu yapamaz stackoverflow.com/a/25564492/23354
Marc Gravell

Yanıtlar:


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

MyEvent += myDelegate;


// .... later

MyEvent -= myDelegate;

Etraftaki delege referans verin.


141

Bir teknik, daha sonra anonim yöntemin içinde mevcut olacak anonim yöntemi tutacak bir değişken bildirmektir. Bu benim için çalıştı çünkü olay ele alındıktan sonra istenen davranış aboneliği iptal etmekti.

Misal:

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

1
Bu tür bir kod kullanarak Resharper, değiştirilmiş bir kapatmaya erişmekten şikayet ediyor ... bu yaklaşım güvenilir mi? Yani, anonim yöntemin gövdesi içindeki 'foo' değişkeninin, anonim yönteme gerçekten atıfta bulunduğundan emin miyiz?
BladeWise

7
Dubluma bir cevap buldum ve 'foo' gerçekten anonim yönteme bir referans tutacak. Yakalanan değişken, anonim yöntem atanmadan önce yakalandığından değiştirilir.
BladeWise

2
Tam da ihtiyacım olan şey bu! = Null eksik. (MyEventHandler foo = temsilci {... MyEvent- = foo;}; MyEvent + = foo; işe yaramadı ...)
TDaver

Resharper 6.1 bir dizi olarak bildirirseniz şikayet etmez. Biraz garip görünüyor, ama bu konuda araçlarime körü körüne güveneceğim: MyEventHandler [] foo = {null}; foo [0] = ... {... MyEvent - = foo [0]; }; MyEvent + = foo [0];
Mike Post

21

Bellekten, anonim yöntemlerle oluşturulan delegelerin denkliği söz konusu olduğunda, spesifikasyon davranışı her iki şekilde de açıkça garanti etmez.

Abonelikten çıkmanız gerekirse, "normal" bir yöntem kullanmanız veya temsilci başka bir yerde tutmanız gerekir; böylece abone olduğunuzda kullandığınız temsilci ile tam olarak aboneliğinizi iptal edebilirsiniz.


Ben Jon, ne arıyorsun? Anlamıyorum. "J c" ile maruz kalan çözüm düzgün çalışmaz mı?
Eric Ouellet

@EricOuellet: Bu cevap temel olarak "temsilci başka bir yerde tut, böylece abone olduğunuzda kullandığınız temsilci ile olan aboneliğinizi iptal edebilirsiniz" ifadesinin bir uygulamasıdır.
Jon Skeet

Jon, özür dilerim, cevabınızı birçok kez ne demek istediğinizi ve "J c" çözümünün abone olmak ve abonelikten çıkmak için aynı delege kullanmadığını anlamaya çalışıyorum, ama bunu yapamıyorum. Belki bana ne dediğini açıklayan bir makaleye işaret edebilirsin? Ününüzü biliyorum ve ne demek istediğinizi gerçekten anlamak istiyorum, bağlantı kurabileceğiniz her şey gerçekten takdir edilecektir.
Eric Ouellet

1
Buldum : msdn.microsoft.com/en-us/library/ms366768.aspx ama anonim kullanmamanızı tavsiye ediyorlar ama büyük bir sorun olduğunu söylemiyorlar mı?
Eric Ouellet

Buldum ... Çok teşekkürler (Michael Blome cevabına bakınız): social.msdn.microsoft.com/Forums/en-US/csharplanguage/thread/…
Eric Ouellet

16

3.0'da kısaltılabilir:

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

15

Yana C # 7.0 yerel fonksiyonları özelliği serbest bırakıldı, yaklaşım önerdi tarafından J c gerçekten temiz hale gelir.

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

Yani, dürüst olmak gerekirse, burada değişken olarak anonim bir fonksiyonunuz yok. Ama bence bunu kullanma motivasyonunuz da yerel işlevlere uygulanabilir.


1
Okunabilirliği daha da iyi hale getirmek için MyEvent + = foo; foo ilanından önce olmak için.
Mark Zhukovsky

9

Herhangi bir delege için bir referans tutmak yerine, etkinliğin çağrı listesini arayan kişiye geri vermek için sınıfınızı düzenleyebilirsiniz. Temel olarak böyle bir şey yazabilirsiniz (MyEvent'in MyClass içinde beyan edildiği varsayılarak):

public class MyClass 
{
  public event EventHandler MyEvent;

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

Böylece tüm çağrı listesine MyClass'ın dışından erişebilir ve istediğiniz herhangi bir işleyicinin aboneliğini iptal edebilirsiniz. Örneğin:

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

Burada bu teknikle ilgili tam bir yazı yazdım .


2
Bu, benden sonra abone olmaları durumunda yanlışlıkla farklı bir örneği (yani ben değil) abonelikten çıkarabileceğim anlamına mı geliyor?
dumbledad

@dumbledad tabii ki bu son olarak kayıtlı olanın kaydını siler. Belirli bir anonim temsilciyi dinamik olarak abonelikten çıkarmak istiyorsanız, bir şekilde tanımlamanız gerekir. O zaman bir referans tutmak öneririz :)
LuckyLikey

Oldukça havalı, ne yapıyorsun, ama bunun yararlı olabileceği bir durum düşünemiyorum. Ama OP'nin Sorusunu gerçekten çözemiyorum. -> +1. IMHO, daha sonra kayıttan çıkarılması gerekirse, isimsiz delegeleri kullanmamalıdır. Onları tutmak aptalca -> daha iyi kullanım Yöntemi. Invocation listesindeki bazı delegeleri kaldırmak oldukça rastgele ve işe yaramaz. Yanlışsam düzelt. :)
LuckyLikey

6

Bir tür topal yaklaşım:

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

  ...

  public event Action OnDoSomething
  {
    add {
      _eventList.Add(value);
    }
    remove {
      _eventList.Remove(value);
    }
  }
}
  1. Olay ekleme / kaldırma yöntemlerini geçersiz kılın.
  2. Bu olay işleyicilerin bir listesini tutun.
  3. Gerektiğinde, hepsini temizleyin ve diğerlerini tekrar ekleyin.

Bu işe yaramayabilir veya en etkili yöntem olabilir, ancak işi halletmek gerekir.


14
Eğer topal olduğunu düşünüyorsanız, yayınlamayın.
Jerry Nixon

2

Aboneliği iptal etmek istiyorsanız, kabul ettiğiniz yanıtta belirtilen rotaya gitmeniz gerekir. Ancak, abone sınıfınız kapsam dışına çıktığında referansları temizlemekten endişe ediyorsanız, zayıf referansların kullanılmasını içeren başka bir (biraz kıvrık) çözüm vardır. Bu konuyla ilgili yeni bir soru ve cevap gönderdim .


2

Basit bir çözüm:

eventhandle değişkenini parametre olarak kendisine iletmeniz yeterlidir. Olay, birden çok iş parçacığı nedeniyle oluşturulan özgün değişkene erişememeniz durumunda aşağıdakini kullanabilirsiniz:

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 iki kez çağrılırsa, daha önce MyEvent -= myEventHandlerInstance;çalıştırılırsa? Mümkünse bir hatayla karşılaşırsınız. Ama böyle olup olmadığından emin değilim.
LuckyLikey

0

Bu temsilci ile bazı nesnelere başvurmak istiyorsanız, Delegate.CreateDelegate (Tür, Nesne hedefi, MethodInfo methodInfo) .net kullanabilirsiniz delegate hedef ve methodInfo göre eşittir düşünün


0

En iyi yol, abone olunan eventHandler üzerinde bir referans tutmaksa, bu bir Sözlük kullanılarak gerçekleştirilebilir.

Bu örnekte, DataGridViews kümesi için mergeColumn parametresini dahil etmek için anonim bir yöntem kullanmam gerekiyor.

Enable parametresi true olarak ayarlanmışken MergeColumn yönteminin kullanılması, olayı false ile kullanırken etkinleştirme özelliğini devre dışı bırakır.

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);
        }
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.