如何获取事件的订阅者?

33

我需要将一个事件的订阅者复制到另一个事件中。我能否获取一个事件的订阅者 (例如 MyEvent[0] 返回一个委托)?

如果无法实现,我将使用 add 访问器将委托添加到列表中。那会是最好的解决方案吗?

4个回答

38

C#事件/委托是多路广播的,因此委托本身就是一个列表。从类内部获取单个调用者,您可以使用:

if (field != null) 
{ 
    // or the event-name for field-like events
    // or your own event-type in place of EventHandler
    foreach(EventHandler subscriber in field.GetInvocationList())
    {
        // etc
    }
}

不过,如果要一次性赋值,只需使用+=或直接赋值:

SomeType other = ...
other.SomeEvent += localEvent;

谢谢指出。这对于我自己代码中的事件最好。 - weiqure
非常感谢您。我需要一个好的解决方案,通过二进制序列化来克隆对象,而不是使用订阅事件,否则我将不得不在几百个类中实现ICloneable接口。 - user1039513

14
如果事件是由另一个类发布的,那么你不能靠它来实现 - 至少不可靠。虽然我们通常认为事件仅仅是委托变量,但它实际上只是一对方法:添加和移除(或订阅和取消订阅)。
如果是自己的代码发布事件,那就容易了 - 你可以让添加/移除访问器执行任何你想要的操作。
请看一下我的关于事件的文章,看看是否有帮助。如果没有,请提供更多详细信息,指定哪些代码部分你能够修改,哪些不能。

8

如果您需要检查外部类事件的订阅者:

EventHandler e = typeof(ExternalClass)
    .GetField(nameof(ExternalClass.Event), BindingFlags.Instance | BindingFlags.NonPublic)
    .GetValue(instanceOfExternalClass) as EventHandler;
if (e != null)
{
    Delegate[] subscribers = e.GetInvocationList();
}

3
更新(感谢评论者):委托不可变性意味着克隆与赋值没有任何区别。当我们写下以下代码时:
myDelegate += AHandler

创建一个全新的委托实例并将其分配给myDelegate

因此,即使没有Clone调用,下面的代码也会完全相同。


MulticastDelegate(基础类型)具有Clone方法。

为了能够获取基础委托,您可能需要避免事件关键字生成的常规帮助程序,并直接管理事物(自定义添加和删除访问器)。

为了展示这一点:

public class Program {
    public delegate void MyDelegate(string name);

    public event MyDelegate EventOne;

    public void HandlerOne(string name) => Console.WriteLine($"This is handler one: {name}");
    public void HandlerTwo(string name) => Console.WriteLine($"This is handler two: {name}");
    public void HandlerThree(string name) => Console.WriteLine($"This is handler three: {name}");

    public void Run() {
        EventOne += HandlerOne;
        EventOne += HandlerTwo;
        Console.WriteLine("Before clone");
        EventOne("EventOne");

        MyDelegate eventTwo = (MyDelegate)EventOne.Clone();
        MyDelegate eventTwo = EventOne;
        Console.WriteLine("After clone copy");
        EventOne("EventOne");
        eventTwo("eventTwo");

        Console.WriteLine("Change event one to show it is different");
        EventOne += HandlerThree;
        EventOne("EventOne");
        eventTwo("eventTwo");
    }
    private static void Main(string[] args) => (new Program()).Run();
}

复制自身相对不重要 - 委托是不可变的,因此您只需复制委托引用即可。 - Jon Skeet
除非像示例中那样,您想要独立修改原始副本。 - Richard
Richard - 不,这个步骤没有 Clone() 也能完全一样地工作。 - Marc Gravell
另外,“能够访问基础委托”的问题——C#规范规定,对于类似字段的事件(简单事件),在类内部任何对事件名称的引用都会被视为对委托字段的引用。 - Marc Gravell
Marc: 当然。(还没喝够咖啡)。(很容易忘记在其他语言中,+= 在 .NET 中不是快捷方式)。 - Richard

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接