ฉันจะล้างการสมัครรับข้อมูลกิจกรรมใน C # ได้อย่างไร


141

รับคลาส C # ต่อไปนี้:

c1 {
 event EventHandler someEvent;
}

หากมีจำนวนมากของการสมัครเป็นสมาชิกc1ของsomeEventเหตุการณ์และฉันต้องการที่จะล้างพวกเขาทุกสิ่งที่เป็นวิธีที่ดีที่สุดเพื่อให้บรรลุนี้? นอกจากนี้ยังพิจารณาว่าการสมัครสมาชิกกับเหตุการณ์นี้อาจเป็น / เป็น lambdas / ผู้ได้รับมอบอำนาจโดยไม่ระบุชื่อ

ปัจจุบันโซลูชันของฉันคือการเพิ่มResetSubscriptions()วิธีการc1ที่กำหนดsomeEventเป็นโมฆะ ฉันไม่รู้ว่าสิ่งนี้มีผลที่มองไม่เห็นหรือไม่

คำตอบ:


181

จากภายในคลาสคุณสามารถตั้งค่าตัวแปร (ซ่อน) เป็นโมฆะ การอ้างอิง Null เป็นวิธีมาตรฐานของการแสดงรายการการร้องขอว่างอย่างมีประสิทธิภาพ

จากนอกห้องเรียนคุณไม่สามารถทำสิ่งนี้ได้ - โดยทั่วไปเหตุการณ์จะเปิดเผย "สมัครสมาชิก" และ "ยกเลิกการสมัคร" และนั่นก็คือ

มันคุ้มค่าที่จะได้ทราบว่าเหตุการณ์ที่เกิดขึ้นจริงเหมือนสนามกำลังทำอะไรพวกเขากำลังสร้างตัวแปรและเหตุการณ์ในเวลาเดียวกัน ภายในชั้นเรียนคุณจะสิ้นสุดการอ้างอิงตัวแปร จากภายนอกคุณอ้างอิงถึงเหตุการณ์

ดูบทความของฉันเกี่ยวกับเหตุการณ์และผู้ได้รับมอบหมายสำหรับข้อมูลเพิ่มเติม


3
หากคุณดื้อคุณสามารถบังคับให้ชัดเจนผ่านการสะท้อน ดูstackoverflow.com/questions/91778/...
ไบรอัน

1
@Brian: มันขึ้นอยู่กับการใช้งาน หากเป็นเพียงเหตุการณ์ที่เกิดขึ้นจริงหรือเป็นเหตุการณ์EventHandlerListคุณอาจทำได้ คุณต้องจำทั้งสองกรณี - และอาจมีการใช้งานอื่น ๆ อีกจำนวนเท่าใด
Jon Skeet

@Joshua: ไม่มันจะตั้งค่าตัวแปรให้มีค่าเป็น null hiddenผมยอมรับว่าตัวแปรจะไม่ถูกเรียก
Jon Skeet

@ JonSkeet นั่นคือสิ่งที่ฉัน (คิด) ฉันพูด วิธีที่เขียนนั้นทำให้ฉันสับสนเป็นเวลา 5 นาที

@JoshuaLamusga: คุณบอกว่ามันจะล้างรายการการเรียกซึ่งดูเหมือนจะแก้ไขวัตถุที่มีอยู่
Jon Skeet

34

เพิ่มวิธีการใน c1 ที่จะตั้ง 'someEvent' เป็นโมฆะ

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

นั่นคือพฤติกรรมที่ฉันเห็น อย่างที่ฉันพูดในคำถามของฉันฉันไม่รู้ว่าฉันมองอะไรบางอย่างหรือเปล่า
โปรแกรมเมอร์

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

มันจะดีกว่าที่จะใช้delegate { }กว่าnullเพื่อหลีกเลี่ยงข้อยกเว้นการอ้างอิงโมฆะ


2
ทำไม? คุณช่วยขยายคำตอบนี้ได้ไหม?
S. Buda

1
@ S.Buda เพราะถ้ามันว่างแล้วคุณจะได้รับการอ้างอิงที่ว่าง มันก็เหมือนกับการใช้VSList.Clear() myList = null
AustinWBryan

6

การตั้งค่าเหตุการณ์ให้เป็นโมฆะภายในคลาสใช้งานได้ เมื่อคุณทิ้งคลาสคุณควรตั้งค่าเหตุการณ์เป็นโมฆะ GC มีปัญหากับกิจกรรมและอาจไม่ล้างคลาสที่จำหน่ายหากมีเหตุการณ์ที่ห้อยอยู่


6

แนวทางที่ดีที่สุดในการล้างสมาชิกทั้งหมดคือการตั้งค่า someEvent เป็นโมฆะโดยการเพิ่มวิธีการสาธารณะอื่นหากคุณต้องการให้ฟังก์ชันนี้เป็นภายนอก สิ่งนี้ไม่มีผลกระทบที่มองไม่เห็น เงื่อนไขที่ต้องจำไว้คือการประกาศให้ผู้ที่มีคำหลัก 'เหตุการณ์'

โปรดดูหนังสือ - C # 4.0 โดยย่อหน้า 125

บางคนเสนอที่นี่เพื่อใช้Delegate.RemoveAllวิธีการ หากคุณใช้รหัสตัวอย่างสามารถทำตามแบบฟอร์มด้านล่าง แต่มันช่างโง่จริงๆ ทำไมไม่เพียงSomeEvent=nullภายในClearSubscribers()ฟังก์ชั่น?

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

5

คุณสามารถทำได้โดยใช้วิธีการมอบสิทธิ์ลบหรือผู้รับมอบสิทธิ์ลบวิธีการทั้งหมด


6
ฉันไม่เชื่อว่าสิ่งนี้จะใช้ได้กับการแสดงออกแลมบ์ดาหรือผู้ได้รับมอบหมายไม่ระบุชื่อ
โปรแกรมเมอร์

3

ความคิดเห็นที่น่าเบื่อขยายแนวคิด

ฉันควรใช้คำว่า "event handler" แทนที่จะเป็น "event" หรือ "มอบหมาย" และใช้คำว่า "เหตุการณ์" สำหรับสิ่งอื่น ๆ ในบางภาษาโปรแกรม (VB.NET, Object Pascal, Objective-C), "event" เรียกว่า "message" หรือ "signal" และยังมีคีย์เวิร์ด "message" และไวยากรณ์น้ำตาลเฉพาะ

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;

และเพื่อตอบสนองต่อ "ข้อความ" นั้นจะเป็นการตอบสนอง "ตัวจัดการเหตุการณ์" ไม่ว่าจะเป็นตัวแทนเดียวหรือหลายผู้รับมอบสิทธิ์

สรุป: "เหตุการณ์" คือ "คำถาม", "ตัวจัดการเหตุการณ์" คือคำตอบ


1

นี่คือทางออกของฉัน:

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

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

คุณต้องโทรDispose()หรือใช้using(new Foo()){/*...*/}รูปแบบเพื่อยกเลิกการเป็นสมาชิกทั้งหมดของรายการการเรียก


0

ลบกิจกรรมทั้งหมดถือว่าเหตุการณ์เป็นประเภท "การกระทำ":

Delegate[] dary = TermCheckScore.GetInvocationList();

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

1
หากคุณอยู่ในประเภทที่ประกาศกิจกรรมที่คุณไม่จำเป็นต้องทำคุณสามารถตั้งค่าเป็นโมฆะหากคุณอยู่นอกประเภทนั้นคุณจะไม่สามารถรับรายการการเรียกร้องของผู้ได้รับมอบหมาย GetInvocationListนอกจากนี้รหัสของคุณพ่นยกเว้นถ้าเหตุการณ์เป็นโมฆะเมื่อโทร
Servy

-1

แทนที่จะเพิ่มและลบการเรียกกลับด้วยตนเองและการประกาศประเภทผู้ร่วมประชุมทุกที่:

// 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);
    }
}

คุณสามารถลองวิธีการทั่วไปนี้:

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();
    }
}

คุณช่วยกรุณาจัดรูปแบบคำถามของคุณและลบช่องว่างด้านซ้ายทั้งหมดได้ไหม? เมื่อคุณคัดลอกและวางจาก IDE สิ่งนี้สามารถเกิดขึ้นได้
AustinWBryan

เพิ่งกำจัดพื้นที่สีขาวนั่นคือสิ่งที่ไม่ดีของฉัน
บาร์เธนดอน
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.