Làm cách nào tôi có thể xóa đăng ký sự kiện trong C #?


141

Tham gia lớp C # sau:

c1 {
 event EventHandler someEvent;
}

Nếu có rất nhiều thuê bao để c1's someEventsự kiện và tôi muốn xóa tất cả, cách tốt nhất để đạt được điều này là gì? Cũng xem xét rằng đăng ký cho sự kiện này có thể là / là lambdas / đại biểu ẩn danh.

Hiện tại giải pháp của tôi là thêm một ResetSubscriptions()phương thức vào c1tập hợp đó someEventthành null. Tôi không biết nếu điều này có bất kỳ hậu quả vô hình.

Câu trả lời:


181

Từ trong lớp, bạn có thể đặt biến (ẩn) thành null. Một tham chiếu null là cách chính tắc để biểu diễn một danh sách gọi trống, một cách hiệu quả.

Từ bên ngoài lớp học, bạn không thể làm điều này - về cơ bản các sự kiện phơi bày "đăng ký" và "hủy đăng ký" và đó là điều đó.

Thật đáng để biết những gì các sự kiện giống như lĩnh vực đang thực sự làm - họ đang tạo ra một biến một sự kiện cùng một lúc. Trong lớp, cuối cùng bạn tham chiếu biến. Từ bên ngoài, bạn tham khảo sự kiện.

Xem bài viết của tôi về các sự kiện và đại biểu để biết thêm thông tin.


3
Nếu bạn bướng bỉnh, bạn có thể buộc nó rõ ràng thông qua sự phản chiếu. Xem stackoverflow.com/questions/91778/ cấp .
Brian

1
@Brian: Nó phụ thuộc vào việc thực hiện. Nếu đó chỉ là một sự kiện giống như một lĩnh vực hoặc một EventHandlerList, bạn có thể có thể. Bạn sẽ phải nhận ra hai trường hợp đó - và có thể có bất kỳ số lượng triển khai nào khác.
Jon Skeet

@Joshua: Không, nó sẽ đặt biến có giá trị null. Tôi đồng ý rằng biến sẽ không được gọi hidden.
Jon Skeet

@JonSkeet Đó là những gì tôi (nghĩ) tôi đã nói. Cách nó được viết làm tôi bối rối trong 5 phút.

@JoshuaLamusga: Vâng, bạn nói rằng nó sẽ xóa một danh sách gọi, nghe có vẻ như sửa đổi một đối tượng hiện có.
Jon Skeet

34

Thêm một phương thức vào c1 sẽ đặt 'someEvent' thành null.

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

Đó là hành vi tôi đang thấy. Như tôi đã nói trong câu hỏi của mình, tôi không biết liệu tôi có đang nhìn thứ gì đó không.
lập trình viên

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

Nó là tốt hơn để sử dụng delegate { }hơn nullđể tránh ngoại lệ ref null.


2
Tại sao? Bạn có thể vui lòng mở rộng về câu trả lời này?
S. Buda

1
@ S.Buda Bởi vì nếu nó là null thì bạn sẽ nhận được một ref. Nó giống như sử dụng một List.Clear()vs myList = null.
AustinWBryan

6

Đặt sự kiện thành null bên trong lớp hoạt động. Khi bạn loại bỏ một lớp, bạn phải luôn đặt sự kiện thành null, GC có vấn đề với các sự kiện và có thể không dọn sạch lớp bị loại bỏ nếu nó có các sự kiện lơ lửng.


6

Cách tốt nhất để xóa tất cả người đăng ký là đặt someEvent thành null bằng cách thêm một phương thức công khai khác nếu bạn muốn đưa chức năng này ra bên ngoài. Điều này không có hậu quả vô hình. Điều kiện tiên quyết là phải nhớ khai báo someEvent bằng từ khóa 'event'.

Vui lòng xem cuốn sách - C # 4.0 trong phần tóm tắt, trang 125.

Một số ở đây đề xuất sử dụng Delegate.RemoveAllphương pháp. Nếu bạn sử dụng nó, mã mẫu có thể theo mẫu dưới đây. Nhưng nó thực sự ngu ngốc. Tại sao không chỉ SomeEvent=nullbên trong ClearSubscribers()chức năng?

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

5

Bạn có thể đạt được điều này bằng cách sử dụng các phương thức Delegate.Remove hoặc Delegate.RemoveAll.


6
Tôi không tin rằng điều này sẽ hoạt động với các biểu thức lambda hoặc đại biểu ẩn danh.
lập trình viên

3

Khái niệm bình luận mở rộng nhàm chán.

Tôi thay vì sử dụng từ "xử lý sự kiện" thay vì "sự kiện" hoặc "đại biểu". Và sử dụng từ "sự kiện" cho những thứ khác. Trong một số ngôn ngữ lập trình (VB.NET, Object Pascal, Objective-C), "sự kiện" được gọi là "thông điệp" hoặc "tín hiệu" và thậm chí có từ khóa "tin nhắn" và cú pháp đường cụ thể.

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;

Và, để trả lời "thông điệp" đó, một "người xử lý sự kiện" trả lời, cho dù là một đại biểu hay nhiều đại biểu.

Tóm tắt: "Sự kiện" là "câu hỏi", "xử lý sự kiện" là câu trả lời.


1

Đây là giải pháp của tôi:

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

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

Bạn cần gọi Dispose()hoặc sử dụng using(new Foo()){/*...*/}mẫu để hủy đăng ký tất cả các thành viên của danh sách gọi.


0

Xóa tất cả các sự kiện, giả sử sự kiện là loại "Hành động":

Delegate[] dary = TermCheckScore.GetInvocationList();

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

1
Nếu bạn thuộc loại đã khai báo sự kiện mà bạn không cần phải làm điều này, bạn chỉ có thể đặt nó thành null, nếu bạn ở ngoài loại đó thì bạn không thể lấy danh sách gọi của đại biểu. Ngoài ra, mã của bạn ném một ngoại lệ nếu sự kiện là null, khi gọi GetInvocationList.
Phục vụ

-1

Thay vì thêm và xóa các cuộc gọi lại theo cách thủ công và có một loạt các loại đại biểu được khai báo ở mọi nơi:

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

Bạn có thể thử phương pháp chung này:

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

Bạn có thể vui lòng định dạng câu hỏi của bạn và loại bỏ tất cả các khoảng trắng bên trái? Khi bạn sao chép và dán từ IDE, điều này có thể xảy ra
AustinWBryan

Chỉ cần thoát khỏi khoảng trắng đó, xấu của tôi
barthdamon
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.