Hủy đăng ký phương thức ẩn danh trong C #


222

Có thể hủy đăng ký một phương thức ẩn danh từ một sự kiện?

Nếu tôi đăng ký một sự kiện như thế này:

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

MyEvent += MyMethod;

Tôi có thể hủy đăng ký như thế này:

MyEvent -= MyMethod;

Nhưng nếu tôi đăng ký bằng một phương thức ẩn danh:

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

Có thể hủy đăng ký phương pháp ẩn danh này? Nếu vậy thì thế nào?


4
Về lý do tại sao bạn không thể làm điều này: stackoverflow.com/a/25564492/23354
Marc Gravell

Câu trả lời:


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

MyEvent += myDelegate;


// .... later

MyEvent -= myDelegate;

Chỉ cần giữ một tài liệu tham khảo cho các đại biểu xung quanh.


141

Một kỹ thuật là khai báo một biến để giữ phương thức ẩn danh mà sau đó sẽ có sẵn bên trong phương thức ẩn danh đó. Điều này làm việc cho tôi vì hành vi mong muốn là hủy đăng ký sau khi sự kiện được xử lý.

Thí dụ:

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

1
Sử dụng loại mã này, Resharper phàn nàn về việc truy cập một bao đóng được sửa đổi ... phương pháp này có đáng tin cậy không? Ý tôi là, chúng tôi có chắc chắn rằng biến 'foo' bên trong phần thân của phương thức ẩn danh, thực sự tham chiếu chính phương thức ẩn danh đó không?
BladeWise

7
Tôi đã tìm thấy câu trả lời cho dubt của mình và đó là 'foo' sẽ thực sự giữ một tham chiếu đến phương thức ẩn danh của nó. Biến bị bắt được sửa đổi, vì nó được bắt trước khi phương thức ẩn danh được gán cho nó.
BladeWise

2
Đó chính xác là những gì tôi cần! Tôi đã thiếu = null. (MyEventHandler foo = ủy nhiệm {... MyEvent- = foo;}; MyEvent + = foo; không hoạt động ...)
TDaver

Resharper 6.1 không phàn nàn nếu bạn khai báo nó như một mảng. Có vẻ hơi kỳ lạ, nhưng tôi sẽ tin tưởng một cách mù quáng các công cụ của mình vào công cụ này: MyEventHandler [] foo = {null}; foo [0] = ... {... MyEvent - = foo [0]; }; MyEvent + = foo [0];
Mike Post

21

Từ bộ nhớ, đặc tả rõ ràng không đảm bảo hành vi theo bất kỳ cách nào khi nói đến sự tương đương của các đại biểu được tạo bằng các phương thức ẩn danh.

Nếu bạn cần hủy đăng ký, bạn nên sử dụng phương thức "bình thường" hoặc giữ lại đại biểu ở một nơi khác để bạn có thể hủy đăng ký với chính xác cùng một đại biểu bạn đã sử dụng để đăng ký.


Tôi Jon, bạn làm gì? Tôi không hiểu giải pháp tiếp xúc với "J c" sẽ không hoạt động đúng không?
Eric Ouellet

@EricOuellet: Câu trả lời đó về cơ bản là một triển khai "giữ lại đại biểu ở một nơi khác để bạn có thể hủy đăng ký với chính xác cùng một đại biểu bạn đã sử dụng để đăng ký".
Jon Skeet

Jon, tôi xin lỗi, tôi đã đọc câu trả lời của bạn nhiều lần cố gắng tìm hiểu ý của bạn và giải pháp "J c" không sử dụng cùng một đại biểu để đăng ký và hủy đăng ký, nhưng tôi không thể hiểu được. Pehaps bạn có thể chỉ cho tôi một bài viết giải thích những gì bạn đang nói? Tôi biết về danh tiếng của bạn và tôi thực sự muốn hiểu ý của bạn, bất cứ điều gì bạn có thể liên kết đến sẽ thực sự được đánh giá cao.
Eric Ouellet

1
Tôi đã tìm thấy: msdn.microsoft.com/en-us/l Library / ms366768.aspx nhưng họ không khuyên bạn nên sử dụng ẩn danh nhưng họ không nói rằng có vấn đề gì lớn không?
Eric Ouellet

Tôi tìm thấy nó ... Cảm ơn rất nhiều (xem Michael BLOME câu trả lời): social.msdn.microsoft.com/Forums/en-US/csharplanguage/thread/...
Eric Ouellet

16

Trong 3.0 có thể rút ngắn thành:

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

15

Vì tính năng chức năng cục bộ C # 7.0 đã được phát hành, cách tiếp cận được đề xuất bởi J c trở nên thực sự gọn gàng.

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

Vì vậy, thành thật mà nói, bạn không có chức năng ẩn danh như một biến ở đây. Nhưng tôi cho rằng động lực để sử dụng nó trong trường hợp của bạn có thể được áp dụng cho các chức năng địa phương.


1
Để làm cho khả năng đọc tốt hơn nữa, bạn có thể di chuyển MyEvent + = foo; dòng để được trước khi tuyên bố của foo.
Mark Zhukovsky

9

Thay vì giữ một tham chiếu đến bất kỳ đại biểu nào, bạn có thể ghi lại lớp của mình để đưa lại danh sách gọi của sự kiện cho người gọi. Về cơ bản, bạn có thể viết một cái gì đó như thế này (giả sử rằng MyEvent được khai báo bên trong MyClass):

public class MyClass 
{
  public event EventHandler MyEvent;

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

Vì vậy, bạn có thể truy cập toàn bộ danh sách gọi từ bên ngoài MyClass và hủy đăng ký bất kỳ trình xử lý nào bạn muốn. Ví dụ:

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

Tôi đã viết một bài đầy đủ về tecnique này ở đây .


2
Điều này có nghĩa là tôi có thể vô tình hủy đăng ký một trường hợp khác (không phải tôi) khỏi sự kiện nếu họ đăng ký sau tôi?
dumbledad

@dumbledad tất nhiên điều này sẽ làm giảm bớt việc đăng ký cuối cùng. Nếu bạn muốn tự động hủy đăng ký một đại biểu ẩn danh cụ thể, bạn cần xác định bằng cách nào đó. Tôi khuyên bạn nên giữ một tài liệu tham khảo sau đó :)
LuckyLikey

Thật tuyệt, những gì bạn đang làm, nhưng tôi không thể tưởng tượng được một trường hợp có thể hữu ích. Nhưng tôi không thực sự giải quyết được câu hỏi của OP. -> +1. IMHO, đơn giản là không nên sử dụng các đại biểu ẩn danh nếu họ sẽ được hủy đăng ký sau này. Giữ chúng là ngu ngốc -> sử dụng tốt hơn Phương pháp. Loại bỏ chỉ một số đại biểu trong danh sách Gọi là khá ngẫu nhiên và vô dụng. Sửa tôi nếu tôi sai. :)
LuckyLikey

6

Cách tiếp cận khập khiễng:

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

  ...

  public event Action OnDoSomething
  {
    add {
      _eventList.Add(value);
    }
    remove {
      _eventList.Remove(value);
    }
  }
}
  1. Ghi đè các phương thức thêm / xóa sự kiện.
  2. Giữ một danh sách những người xử lý sự kiện.
  3. Khi cần, xóa tất cả chúng và thêm lại những cái khác.

Điều này có thể không hoạt động hoặc là phương pháp hiệu quả nhất, nhưng nên hoàn thành công việc.


14
Nếu bạn nghĩ nó khập khiễng, đừng đăng nó.
Jerry Nixon

2

Nếu bạn muốn có thể kiểm soát việc hủy đăng ký thì bạn cần đi theo lộ trình được nêu trong câu trả lời được chấp nhận của bạn. Tuy nhiên, nếu bạn chỉ quan tâm đến việc xóa các tham chiếu khi lớp đăng ký của bạn vượt quá phạm vi, thì có một giải pháp khác (hơi phức tạp) liên quan đến việc sử dụng các tham chiếu yếu. Tôi vừa đăng một câu hỏi và trả lời về chủ đề này.


2

Một giải pháp đơn giản:

chỉ cần truyền biến eventhandle làm tham số cho chính nó. Sự kiện nếu bạn gặp trường hợp bạn không thể truy cập vào biến được tạo ban đầu do đa luồng, bạn có thể sử dụng điều này:

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!");
}

Điều gì xảy ra nếu MyEvent được gọi hai lần, trước khi MyEvent -= myEventHandlerInstance;được chạy? Nếu có thể, bạn sẽ gặp lỗi. Nhưng tôi không chắc đó là trường hợp.
LuckyLikey

0

nếu bạn muốn tham khảo một số đối tượng với ủy nhiệm này, có thể bạn có thể sử dụng Delegate.CreateDelegate (Loại, mục tiêu đối tượng, Phương thứcInInfo) .net xem xét đại biểu bằng với đích và phương thứcInfo


0

Nếu cách tốt nhất là giữ một tham chiếu về eventHandler đã đăng ký, điều này có thể đạt được bằng cách sử dụng Từ điển.

Trong ví dụ này, tôi phải sử dụng một phương thức ẩn danh để bao gồm tham số mergeColumn cho một tập hợp DataGridViews.

Sử dụng phương thức MergeColumn với tham số enable được đặt thành true cho phép sự kiện trong khi sử dụng nó với vô hiệu hóa sai.

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