7

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?

MakePeaceGreatAgain
  • 35,491
  • 6
  • 60
  • 111
  • 1
    It works with `myEvent`. I guess because the `MyEvent` is a facade for the real event. – Patrick Hofman Oct 29 '18 at 08:49
  • @PatrickHofman I already know that it works with `myEvent`. And I already **guessed** the same. – MakePeaceGreatAgain Oct 29 '18 at 08:50
  • 1
    Don't know the details or specs, but I assume `add/remove` is not the same as `get/set`, it's rather translated to `void AddToInvocationList(delegate...);` and `void RemoveFromInvocationList(...);`, so the inner `EventHandler` field is not exposed to the consumer of the class. – René Vogt Oct 29 '18 at 08:50
  • 1
    @Adrian While the duplicate answers the part "what is the solution" (which was not even my question), it does not answer my question "**why** can we do the one but not the other". – MakePeaceGreatAgain Oct 29 '18 at 08:54
  • 1
    The IL implementation of manual/auto add/remove is the same. But the invoke on the event is done on the field. So the public event is replace with a field and invoked on that field – Jeroen van Langen Oct 29 '18 at 08:56
  • @HimBromBeere Why? Do you mean this part of the spec? https://github.com/dotnet/csharplang/blob/master/spec/classes.md#field-like-events – adjan Oct 29 '18 at 08:58
  • @HimBromBeere I agree with you, they might add this to the syntactic sugar, that the event is replaced on compile-time with a field. At the moment it looks inconsistent. – Jeroen van Langen Oct 29 '18 at 10:28
  • @Adrian Yeap, that was exactly what I was looking for. – MakePeaceGreatAgain Oct 29 '18 at 11:14

4 Answers4

4

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.

Jeroen van Langen
  • 21,446
  • 3
  • 42
  • 57
2

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.

Patrick Hofman
  • 153,850
  • 22
  • 249
  • 325
1

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)

adjan
  • 13,371
  • 2
  • 31
  • 48
0

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.

Barr J
  • 10,636
  • 1
  • 28
  • 46
  • 1
    Out of the scope, what about singlethreaded applications? This is about syntactic sugar of C#, which replaces `event` declarations to methods and fields. – Jeroen van Langen Oct 29 '18 at 09:06
  • First I made changes to the code, just to notify :) Second, event with single threaded applications, it is always a good practice to lock your event handlers when dealing with delegates. It is an extra precaution to make sure everything will work properly. – Barr J Oct 29 '18 at 09:09
  • This also means that you should lock it when invoking. Like lock/create a copy/unlock and execute. I'd rather not add/remove event handlers cross threading. – Jeroen van Langen Oct 29 '18 at 10:27
  • This is, as they like to say, to the eyes of the beholder :) – Barr J Oct 29 '18 at 10:41