在 C# 中,为什么我不能测试事件处理程序在它定义的类之外的任何地方是否为空?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1246116/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 14:10:31  来源:igfitidea点击:

In C#, why can't I test if a event handler is null anywhere outside of the class that it's defined?

c#eventsevent-handlingdelegates

提问by gabe

I am sure that I am just not understanding something fundamental about events and/or delegates in C#, but why can't I do the Boolean tests in this code sample:

我确信我只是不了解 C# 中关于事件和/或委托的一些基本知识,但为什么我不能在此代码示例中进行布尔测试:

public class UseSomeEventBase {
    public delegate void SomeEventHandler(object sender, EventArgs e);
    public event SomeEventHandler SomeEvent;
    protected void OnSomeEvent(EventArgs e) {
        // CANONICAL WAY TO TEST EVENT. OF COURSE, THIS WORKS.
        if (SomeEvent != null) SomeEvent(this, e);
    }
}

public class UseSomeEvent : UseSomeEventBase {
    public bool IsSomeEventHandlerNull() {
        // "LEFT HAND SIDE" COMPILER ERROR
        return SomeEvent == null;
    }
}

class Program {
    static void Main(string[] args) {
        var useSomeEvent = new UseSomeEvent();
        useSomeEvent.SomeEvent +=new UseSomeEventBase.SomeEventHandler(FuncToHandle);
        // "LEFT HAND SIDE" COMPILER ERROR
        if (useSomeEvent.SomeEvent == null) {

        }
        var useSomeEventBase = new UseSomeEventBase();
        useSomeEventBase.SomeEvent += new UseSomeEventBase.SomeEventHandler(FuncToHandle);
        // "LEFT HAND SIDE" COMPILER ERROR
        if (useSomeEventBase.SomeEvent == null) {

        }
    }

    static void FuncToHandle(object sender, EventArgs e) { }
}

采纳答案by Jon Skeet

An event is really just an "add" operation and a "remove" operation. You can't get the value, you can't set the value, you can't call it - you can just subscribe a handler for the event (add) or unsubscribe one (remove). This is fine - it's encapsulation, plain and simple. It's up to the publisher to implement add/remove appropriately, but unless the publisher chooses to make the details available, subscribers can't modify or access the implementation-specific parts.

事件实际上只是一个“添加”操作和一个“删除”操作。您无法获取值,无法设置值,无法调用它 - 您可以只订阅事件处理程序 ( add) 或取消订阅 ( remove)。这很好 - 它是封装,简单明了。适当地实现添加/删除由发布者决定,但除非发布者选择提供详细信息,否则订阅者无法修改或访问特定于实现的部分。

Field-like eventsin C# (where you don't specify the add/remove bits) hide this - they create a variable of a delegate type andan event. The event's add/remove implementations just use the variable to keep track of the subscribers.

C# 中的类似字段的事件(您不指定添加/删除位)隐藏了这一点 - 它们创建了一个委托类型的变量一个事件。事件的添加/删除实现只使用变量来跟踪订阅者。

Inside the class you refer to the variable (so you can get the currently subscribed delegates, execute them etc) and outside the class you refer to the event itself (so only have add/remove abilities).

在类内部,您引用变量(因此您可以获得当前订阅的委托,执行它们等),而在类外部,您引用事件本身(因此只有添加/删除功能)。

The alternative to field-like events is where you explicitly implement the add/remove yourself, e.g.

类似字段的事件的替代方法是您明确地实现添加/删除自己,例如

private EventHandler clickHandler; // Normal private field

public event EventHandler Click
{
    add
    {
        Console.WriteLine("New subscriber");
        clickHandler += value;
    }
    remove
    {
        Console.WriteLine("Lost a subscriber");
        clickHandler -= value;
    }
}

See my article on eventsfor more information.

有关更多信息,请参阅我关于事件的文章

Of course the event publisher canalso make more information available - you could write a property like ClickHandlersto return the current multi-cast delegate, or HasClickHandlersto return whether there are any or not. That's not part of the core event model though.

当然,事件发布也可以提供更多信息——你可以编写一个属性,比如ClickHandlers返回当前的多播委托,或者HasClickHandlers返回是否有任何委托。但这不是核心事件模型的一部分。

回答by Darthg8r

You'd have to do that from the base class. That's the exact reason that you did this:

你必须从基类中做到这一点。这就是你这样做的确切原因:

protected void OnSomeEvent(EventArgs e) {
    // CANONICAL WAY TO TEST EVENT. OF COURSE, THIS WORKS.
    if (SomeEvent != null) SomeEvent(this, e);
}

You can't access events from a derived class. Also, you should make that method virtual, so that it can be overridden in a derived class.

您不能从派生类访问事件。此外,您应该使该方法成为虚拟方法,以便它可以在派生类中被覆盖。

回答by JaredPar

Here's a slightly different question

这是一个稍微不同的问题

What value is there in testing an externally defined event for null?

测试外部定义的事件是否为空有什么价值?

As an external consumer of an event you can only do 2 operations

作为事件的外部消费者,您只能执行 2 项操作

  • Add a handler
  • Remove a handler
  • 添加处理程序
  • 删除处理程序

The null or non-nullness of the event has no bearing on these 2 actions. Why do you want to run a test which provides no perceivable value?

事件的 null 或 non-nullness 与这两个操作无关。你为什么要运行一个没有提供可感知价值的测试?

回答by Zensar

It's a rule in place when using the 'event' keyword. When you create an event, you are restricting outside class interaction with the delegate to a "subscribe / unsubscribe" relationship, this includes cases of inheritance. Remember an event is essentially a property, but for method calls, it isn't really an object itself, so really it looks more like this:

这是使用“事件”关键字时的规则。创建事件时,您将外部类与委托的交互限制为“订阅/取消订阅”关系,这包括继承的情况。请记住,事件本质上是一个属性,但对于方法调用而言,它本身并不是一个真正的对象,因此它看起来更像这样:

public event SomeEventHandler SomeEvent
{
     add
     {
          //Add method call to delegate
     }
     remove
     {
          //Remove method call to delegate
     }
}

回答by Sunil

You can easily use a very simple approach here to not repeatedly subscribe to an event.

您可以在此处轻松使用非常简单的方法来不重复订阅事件。

Either of the 2 approaches below can be used:

可以使用以下两种方法中的任何一种:

  1. Flag approach: _getWarehouseForVendorCompletedSubscribed is a private variable initialized to false.

        if (!_getWarehouseForVendorCompletedSubscribed)
        {
            _serviceClient.GetWarehouseForVendorCompleted += new EventHandler<GetWarehouseForVendorCompletedEventArgs>(_serviceClient_GetWarehouseForVendorCompleted);
            _getWarehouseForVendorCompletedSubscribed = true;
        }
    
  2. Unsubscribe Approach:Include an unsubscribe everytime you want to subscribe.

    _serviceClient.GetWarehouseForVendorCompleted -= new 
        EventHandler<GetWarehouseForVendorCompletedEventArgs>  
     (_serviceClient_GetWarehouseForVendorCompleted);
    
    
    _serviceClient.GetWarehouseForVendorCompleted += new 
           EventHandler<GetWarehouseForVendorCompletedEventArgs>
           (_serviceClient_GetWarehouseForVendorCompleted);
    
  1. 标志方法:_getWarehouseForVendorCompletedSubscribed 是初始化为 false 的私有变量。

        if (!_getWarehouseForVendorCompletedSubscribed)
        {
            _serviceClient.GetWarehouseForVendorCompleted += new EventHandler<GetWarehouseForVendorCompletedEventArgs>(_serviceClient_GetWarehouseForVendorCompleted);
            _getWarehouseForVendorCompletedSubscribed = true;
        }
    
  2. 取消订阅方法:每次要订阅时都包括取消订阅。

    _serviceClient.GetWarehouseForVendorCompleted -= new 
        EventHandler<GetWarehouseForVendorCompletedEventArgs>  
     (_serviceClient_GetWarehouseForVendorCompleted);
    
    
    _serviceClient.GetWarehouseForVendorCompleted += new 
           EventHandler<GetWarehouseForVendorCompletedEventArgs>
           (_serviceClient_GetWarehouseForVendorCompleted);
    

回答by Tom

Here the answer:

答案在这里:

using System;
delegate void MyEventHandler();
class MyEvent
{
    string s;
    public event MyEventHandler SomeEvent;
    // This is called to raise the event.
    public void OnSomeEvent()
    {
        if (SomeEvent != null)
        {
            SomeEvent();
        }

    }

    public string IsNull
    {
        get
        {
            if (SomeEvent != null)
                 return s = "The EventHandlerList is not NULL";
            else return s = "The EventHandlerList is NULL"; ;
        }
    }
}

class EventDemo
{  
    // An event handler.
    static void Handler()
    {
       Console.WriteLine("Event occurred");
    }

    static void Main()
    {
       MyEvent evt = new MyEvent();
       // Add Handler() to the event list.
       evt.SomeEvent += Handler;
       // Raise the event.
       //evt.OnSomeEvent();
       evt.SomeEvent -= Handler;
       Console.WriteLine(evt.IsNull);
       Console.ReadKey();
   }
} 

回答by user3202543

Publisher of the event implicitly overload only +=and -=operations, and other operations are not implemented in the publisher because of the obvious reasons as explained above, such as don't want to give control to subscriber to change events.

事件的发布者仅隐式重载+=-=操作,其他操作没有在发布者中实现,原因如上文所述,例如不想将控制权交给订阅者更改事件。

If we want to validate if a particular event is subscribed in the subscriber class, better publisher will set a flag in its class when event is subscriber and clear the flag when it is unsubscriber.

如果我们想验证订阅者类中是否订阅了特定事件,更好的发布者将在事件是订阅者时在其类中设置一个标志,并在取消订阅者时清除该标志。

If subscriber can access the flag of publisher, very easily identifiable whether the particular event is subscriber or not by checking the flag value.

如果订阅者可以访问发布者的标志,通过检查标志值很容易识别特定事件是否是订阅者。