简体   繁体   中英

Why can´t we raise event with accessors?

Why can´t we raise an event with a custom implementation, while it is possible without them? See this code:

public class Program
{
    private EventHandler myEvent;
    public event EventHandler MyEvent
    {
        add { myEvent += value; }
        remove { myEvent -= value; }
    }

    public event EventHandler AnotherEvent;

    public static void Main()
    {
        var target = new Program();
        target.MyEvent(null, null);       // ERROR CS0079
        target.AnotherEvent(null, null);  // compiles
    }
}

You see both events are declared within my class. While target.AnotherEvent(...) compiles just fine, target.MyEvent(...) does not:

The Event MyEvent can only appear on the left hand side of += or -=.

I Know an event is just a delegate with an add- and remove-method. So AnotherEvent is translated by the compiler to an add- and a remove-method:

private EventHandler _AnotherEvent;
public event EventHandler AnotherEvent
{ 
    add { _AnotherEvent += value; }
    remove { _AnotherEvent -= value; }
}

So I assume the call to AnotherEvent is replaced by the compiler to a call to the private delegate, which was _AnotherEvent(...) .

Did I get this right? Are there any docs about why the second call works while the former does not? Or at least any description about what the compiler does here?

When an auto event is used public event EventHandler AnotherEvent; . The compiler will create a field (and some methods) for it and invoking is done on that field. So the public event does not exists anymore. It's syntactic sugar.

So invoking a non-auto event is not possible. Because it isn't found in the compiled code. It's replaced by add_ , remove_ methods. You can only invoke on the private field (which is generated)

This explains why you cannot invoke an event outside the class instance.

It doesn't work because there is simply now way to get the actual invokeable event handler. As you have noted, there is just an add and remove , not a get .

The generated code for the event handler is:

.event [mscorlib]System.EventHandler MyEvent
{
  .addon instance void ConsoleApp1.Program::add_MyEvent(class [mscorlib]System.EventHandler)
  .removeon instance void ConsoleApp1.Program::remove_MyEvent(class [mscorlib]System.EventHandler)
} // end of event Program::MyEvent

It adds two method references, one for add and one for remove . If you look at it, how would it know what method to invoke? What if add and remove are much more complex than they are now? There is just no way to know for sure what event handler to call.

It's syntactical sugar. That you can call AnotherEvent like the backing field is a convenience provided by the compiler ( AnotherEvent is a so-called field-like event ). Once you add your own accessors, the event declaration ceases to be a field-like event and has to be invoked through its backing field.

See the relevant part of the C# Language Specification :

Field-like events

Within the program text of the class or struct that contains the declaration of an event, certain events can be used like fields. To be used in this way, an event must not be abstract or extern, and must not explicitly include event_accessor_declarations. Such an event can be used in any context that permits a field. The field contains a delegate (Delegates) which refers to the list of event handlers that have been added to the event. If no event handlers have been added, the field contains null.

(emphasis mine)

It is recommended that you lock the event before you add or remove a new event handler method.

saying that, have a look on this piece of code:

public event EventHandler MyEvent
{
    add
    {
        lock (objectLock)
        {
            myEvent += value;
        }
    }
    remove
    {
        lock (objectLock)
        {
            myEvent -= value;
        }
    }
}

The reason public event EventHandler AnotherEvent; works is because When no custom event accessors are supplied in your code, the compiler will add them automatically.

Follow this doc, How to: Implement Custom Event Accessors in order to get more details about the proper implementation and this post for another source.

Regarding the implementation:

 private EventHandler myEvent;
 public event EventHandler MyEvent
    {
        add
        {
            lock (objectLock)
            {
                myEvent += value;
            }
        }
        remove
        {
            lock (objectLock)
            {
                myEvent -= value;
            }
        }
    }

    public event EventHandler AnotherEvent;

    public static void Main()
    {
        var target = new Program();
        var myEvent =  target.MyEvent;
        myEvent?.Invoke(EventArgs.Empty, EventArgs.Empty);      
        target.AnotherEvent(null, null); 
    }

Edit to explain the implementation:

var myEvent =  target.MyEvent;

With an explicit event, you have to provide your own backing store - either a delegate field or something like EventHandlerList , so we just go with var here.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM