C # 'daki etkinlik aboneliklerini nasıl temizleyebilirim?


141

Aşağıdaki C # sınıfını alın:

c1 {
 event EventHandler someEvent;
}

Orada abonelikleri bir sürü varsa c1bireyin someEventolay ve hepsini temizlemek istediğiniz, bunu başarmak için en iyi yolu nedir? Ayrıca bu etkinliğe aboneliklerin lambdas / anonim delege olabileceğini de göz önünde bulundurun.

Şu anda benim çözüm null ResetSubscriptions()için c1bu kümelere bir yöntem eklemektir someEvent. Bunun görülmeyen sonuçları olup olmadığını bilmiyorum.

Yanıtlar:


181

Sınıf içinde, (hidden) değişkenini null olarak ayarlayabilirsiniz. Boş referans, boş bir çağrı listesini etkin bir şekilde temsil etmenin kanonik yoludur.

Sınıfın dışında bunu yapamazsınız - etkinlikler temel olarak "abone" ve "abonelikten çıkma" yı ortaya çıkarır ve hepsi bu kadar.

Alan benzeri olayların gerçekte ne yaptığını bilmeye değer - aynı zamanda bir değişken ve bir olay yaratıyorlar . Sınıf içinde, değişkeni referans alırsınız. Dışarıdan, etkinliğe başvuruyorsunuz.

Daha fazla bilgi için etkinlikler ve delegeler hakkındaki makaleme bakın .


3
İnatçıysanız, yansımayla netleştirebilirsiniz. Bkz. Stackoverflow.com/questions/91778/… .
Brian

1
@Brian: Uygulamaya bağlı. Bu yalnızca saha benzeri bir etkinlik veya bir ise EventHandlerList, bunu yapabilirsiniz. Yine de bu iki vakayı tanımanız gerekir - ve başka birçok uygulama olabilir.
Jon Skeet

@Joshua: Hayır, değişkeni null değerine ayarlayacak. Değişkenin çağrılmayacağını kabul ediyorum hidden.
Jon Skeet

@JonSkeet Ben de öyle dedim (düşündüm). Yazılma şekli beni 5 dakika karıştırdı.

@JoshuaLamusga: Peki, mevcut bir nesneyi değiştirmek gibi görünen bir çağrı listesini temizleyeceğini söylediniz.
Jon Skeet

34

C1'e 'someEvent' değerini null değerine ayarlayacak bir yöntem ekleyin.

public class c1
{
    event EventHandler someEvent;
    public ResetSubscriptions() => someEvent = null;    
}

Gördüğüm davranış bu. Sorumda söylediğim gibi, bir şeye bakıp bakmadığımı bilmiyorum.
programcı

8
class c1
{
    event EventHandler someEvent;
    ResetSubscriptions() => someEvent = delegate { };
}

Kullanımı daha iyidir delegate { }daha nullboş ref istisna önlemek için.


2
Neden? Lütfen bu cevabı genişletebilir misiniz?
S. Buda

1
@ S.Buda Çünkü boşsa, boş bir ref elde edersiniz. Bir List.Clear()vs kullanmak gibidir myList = null.
AustinWBryan

6

Sınıf içinde olayı null değerine ayarlamak çalışır. Bir sınıfı bertaraf ettiğinizde, etkinliği her zaman null olarak ayarlamanız gerekir, GC'nin olaylarla ilgili sorunları vardır ve sarkan olayları varsa atılan sınıfı temizlemeyebilir.


6

Tüm aboneleri temizlemenin en iyi yolu, bu işlevi dışarıya maruz bırakmak istiyorsanız, başka bir genel yöntem ekleyerek someEvent öğesini null değerine ayarlamaktır. Bunun görülmeyen sonuçları yoktur. Önkoşul, 'event' anahtar sözcüğüyle SomeEvent bildirmeyi hatırlamaktır.

Lütfen kısaca, C # 4.0 kitabına bakınız, sayfa 125.

Burada bazıları Delegate.RemoveAllyöntem kullanmayı önerdi . Kullanırsanız, örnek kod aşağıdaki formu izleyebilir. Ama bu gerçekten aptalca. Neden sadece fonksiyonun SomeEvent=nulliçinde değil ClearSubscribers()?

public void ClearSubscribers ()
{
   SomeEvent = (EventHandler) Delegate.RemoveAll(SomeEvent, SomeEvent);
   // Then you will find SomeEvent is set to null.
}

5

Delegate.Remove veya Delegate.RemoveAll yöntemlerini kullanarak bunu başarabilirsiniz.


6
Bunun lambda ifadeleri veya anonim delegelerle çalışacağına inanmıyorum.
programcı

3

Kavramsal uzatılmış sıkıcı yorum.

Bunun yerine "olay" veya "temsilci" yerine "olay işleyici" kelimesini kullanıyorum. Ve diğer şeyler için "olay" kelimesini kullandı. Bazı programlama dillerinde (VB.NET, Object Pascal, Objective-C), "event", "mesaj" veya "sinyal" olarak adlandırılır ve hatta bir "message" anahtar kelimesi ve belirli şeker sözdizimine sahiptir.

const
  WM_Paint = 998;  // <-- "question" can be done by several talkers
  WM_Clear = 546;

type
  MyWindowClass = class(Window)
    procedure NotEventHandlerMethod_1;
    procedure NotEventHandlerMethod_17;

    procedure DoPaintEventHandler; message WM_Paint; // <-- "answer" by this listener
    procedure DoClearEventHandler; message WM_Clear;
  end;

Ve bu "mesaja" yanıt vermek için, ister tek bir delege isterse birden fazla delege olsun, bir "olay işleyici" yanıt verir.

Özet: "Olay", "soru" dur, "olay işleyicileri" cevaplardır.


1

Bu benim çözümüm:

public class Foo : IDisposable
{
    private event EventHandler _statusChanged;
    public event EventHandler StatusChanged
    {
        add
        {
            _statusChanged += value;
        }
        remove
        {
            _statusChanged -= value;
        }
    }

    public void Dispose()
    {
        _statusChanged = null;
    }
}

Tüm çağrı listesinin üyeliğini iptal etmek için arama yapmanız Dispose()veya using(new Foo()){/*...*/}kalıbı kullanmanız gerekir .


0

Tüm etkinlikleri kaldırın, etkinliğin "İşlem" türü olduğunu varsayalım:

Delegate[] dary = TermCheckScore.GetInvocationList();

if ( dary != null )
{
    foreach ( Delegate del in dary )
    {
        TermCheckScore -= ( Action ) del;
    }
}

1
Bunu yapmanız gerekmediğini bildiren türün içindeyseniz, bunu yalnızca null olarak ayarlayabilirsiniz, türün dışındaysanız, temsilcinin çağrı listesini alamazsınız. Ayrıca, olay çağrılırken kod boşsa kodunuz bir istisna atar GetInvocationList.
13'te

-1

Geri aramaları manuel olarak eklemek ve kaldırmak ve her yerde bir grup temsilci türü belirtmek yerine:

// The hard way
public delegate void ObjectCallback(ObjectType broadcaster);

public class Object
{
    public event ObjectCallback m_ObjectCallback;
    
    void SetupListener()
    {
        ObjectCallback callback = null;
        callback = (ObjectType broadcaster) =>
        {
            // one time logic here
            broadcaster.m_ObjectCallback -= callback;
        };
        m_ObjectCallback += callback;

    }
    
    void BroadcastEvent()
    {
        m_ObjectCallback?.Invoke(this);
    }
}

Bu genel yaklaşımı deneyebilirsiniz:

public class Object
{
    public Broadcast<Object> m_EventToBroadcast = new Broadcast<Object>();

    void SetupListener()
    {
        m_EventToBroadcast.SubscribeOnce((ObjectType broadcaster) => {
            // one time logic here
        });
    }

    ~Object()
    {
        m_EventToBroadcast.Dispose();
        m_EventToBroadcast = null;
    }

    void BroadcastEvent()
    {
        m_EventToBroadcast.Broadcast(this);
    }
}


public delegate void ObjectDelegate<T>(T broadcaster);
public class Broadcast<T> : IDisposable
{
    private event ObjectDelegate<T> m_Event;
    private List<ObjectDelegate<T>> m_SingleSubscribers = new List<ObjectDelegate<T>>();

    ~Broadcast()
    {
        Dispose();
    }

    public void Dispose()
    {
        Clear();
        System.GC.SuppressFinalize(this);
    }

    public void Clear()
    {
        m_SingleSubscribers.Clear();
        m_Event = delegate { };
    }

    // add a one shot to this delegate that is removed after first broadcast
    public void SubscribeOnce(ObjectDelegate<T> del)
    {
        m_Event += del;
        m_SingleSubscribers.Add(del);
    }

    // add a recurring delegate that gets called each time
    public void Subscribe(ObjectDelegate<T> del)
    {
        m_Event += del;
    }

    public void Unsubscribe(ObjectDelegate<T> del)
    {
        m_Event -= del;
    }

    public void Broadcast(T broadcaster)
    {
        m_Event?.Invoke(broadcaster);
        for (int i = 0; i < m_SingleSubscribers.Count; ++i)
        {
            Unsubscribe(m_SingleSubscribers[i]);
        }
        m_SingleSubscribers.Clear();
    }
}

Lütfen sorunuzu biçimlendirebilir ve soldaki tüm beyaz alanı kaldırabilir misiniz? Bir IDE'den kopyalayıp yapıştırdığınızda bu olabilir
AustinWBryan

O boşluktan kurtuldum, benim kötü
barthdamon
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.