Open sandboxFocusImprove this doc

Overriding events

Metalama allows you to override the three semantics of events: add, remove, and invoke.

To override an event, you can use one of the following approaches:

Overriding the add and remove accessors

Overriding the add and remove accessors of events follows a similar process to overriding properties.

If you attempt to override a field-like event, it is transformed into an explicitly implemented event and its backing field — just as happens with automatic properties.

Example: Logging

The following example demonstrates overriding the add and remove accessors of events, without overriding the invoke operation. The example aspect logs the operation of adding and removing handlers to an event. It is applied to both a field-like and an explicitly-implemented event. You can compare the code transformation pattern.

1using Metalama.Framework.Aspects;
2using System;
3
4namespace Doc.EventLogging;
5
6public class LogAttribute : OverrideEventAspect
7{
8    public override void OverrideAdd( dynamic handler )
9    {
10        Console.WriteLine( $"Adding handler {((Delegate) handler).Method}." );
11        meta.Proceed();
12    }
13
14    public override void OverrideRemove( dynamic handler )
15    {
16        Console.WriteLine( $"Removing handler {((Delegate) handler).Method}." );
17        meta.Proceed();
18    }
19}
Source Code
1using System;
2
3namespace Doc.EventLogging;
4
5public class Camera
6{
7    private EventHandler? _lightingChanged;
8
9    // Field-like event.
10    [Log]


11    public event EventHandler? FocusChanged;
12
13    private void OnFocusChanged()






14    {







15        this.FocusChanged?.Invoke( this, EventArgs.Empty );
16    }
17
18    // Explicitly-implemented event.
19    [Log]
20    public event EventHandler? LightingChanged
21    {
22        add => this._lightingChanged += value;
23        remove => this._lightingChanged -= value;







24    }
25

26    private void OnLightingChanged()
27    {
28        this._lightingChanged?.Invoke( this, EventArgs.Empty );
29    }
30}
Transformed Code
1using System;
2
3namespace Doc.EventLogging;
4
5public class Camera
6{
7    private EventHandler? _lightingChanged;
8
9    private event EventHandler? _focusChanged;
10
11    // Field-like event.
12    [Log]
13    public event EventHandler? FocusChanged
14    {
15        add
16        {
17            Console.WriteLine($"Adding handler {value.Method}.");
18            this._focusChanged += value;
19        }
20
21        remove
22        {
23            Console.WriteLine($"Removing handler {value.Method}.");
24            this._focusChanged -= value;
25        }
26    }
27
28    private void OnFocusChanged()
29    {
30        this._focusChanged?.Invoke(this, EventArgs.Empty);
31    }
32
33    // Explicitly-implemented event.
34    [Log]
35    public event EventHandler? LightingChanged
36    {
37        add
38        {
39            Console.WriteLine($"Adding handler {value.Method}.");
40            this._lightingChanged += value;
41        }
42        remove
43        {
44            Console.WriteLine($"Removing handler {value.Method}.");
45            this._lightingChanged -= value;
46        }
47    }
48
49    private void OnLightingChanged()
50    {
51        this._lightingChanged?.Invoke(this, EventArgs.Empty);
52    }
53}

Overriding the invoke operation

Most of the time, advising an event requires overriding its invoke operation. For instance, if you want to swallow exceptions in event handlers or execute events in a background thread, it's best to do so by overriding the invoke semantic.

To override the invoke semantic, implement the OverrideEventAspect.OverrideInvoke method or supply an invokeTemplate argument to the OverrideAccessors method.

Note

The OverrideInvoke advice is invoked once per event handler. If there are 3 event handlers and the event is invoked once, the OverrideInvoke advice will be invoked 3 times (see graph below).

Adding/removing event handlers from an advice

If you are writing an exception handling aspect, you'll want to unregister the event handler from the invoke template. You can do this by invoking the Remove method from the template, for instance:

meta.Target.Event.Remove( handler );
Warning

The Raise method is not implemented yet.

Limitations

  • Delegate signatures with a non-void return type or with out and ref parameters are not supported.
  • Using meta.Target.Event.Raise() from the OverrideInvoke template is not supported. You must use meta.Proceed().
  • Only handlers added through the event's add and remove accessors will be intercepted by the raise advice. Handlers added differently, for instance those added directly to the event backing field, won't be intercepted.

Example: Safe events

The following aspect implements a "Fool me once, shame on you; fool me twice, shame on me" pattern that handles exceptions in each event handler individually and unregisters any unreliable handler.

1using Metalama.Framework.Aspects;
2using System;
3
4namespace Doc.SafeEvent_;
5
6public class SafeEventAttribute : OverrideEventAspect
7{
8    public override dynamic? OverrideInvoke( dynamic handler )
9    {
10        try
11        {
12            return meta.Proceed();
13        }
14        catch ( Exception e )
15        {
16            // Log the error.
17            Console.WriteLine( e );
18
19            // Remove the faulted event handler.
20            meta.Target.Event.Remove( handler );
21
22            return null;
23        }
24    }
25}
Source Code
1using System;
2
3namespace Doc.SafeEvent_;
4

5public class Camera
6{
7    private EventHandler? _lightingChanged;
8
9    // Field-like event.












10    [SafeEvent]




11    public event EventHandler? FocusChanged;
12
13    private void OnFocusChanged()
14    {

























15        this.FocusChanged?.Invoke( this, EventArgs.Empty );














16    }
17
18    // Explicitly-implemented event.
19    [SafeEvent]
20    public event EventHandler? LightingChanged


21    {
22        add => this._lightingChanged += value;
23        remove => this._lightingChanged -= value;
24    }
25
26    private void OnLightingChanged()






















27    {
28        this._lightingChanged?.Invoke( this, EventArgs.Empty );














29    }
30}
31
Transformed Code
1using System;
2using Metalama.Framework.RunTime.Events;
3
4namespace Doc.SafeEvent_;
5
6public class Camera
7{
8    private static readonly DelegateEventAdapter<EventHandler, (object?, EventArgs), Camera> FocusChangedAdapter_0 = new(
9    static (handler, ref args, me) => me.FocusChanged_Invoke_SafeEvent(handler, ref args),
10    static b => (sender, e) => b.Invoke((sender, e)),
11    static (handler, me) => me.FocusChanged_SafeEvent += handler,
12    static (handler, me) => me.FocusChanged_SafeEvent -= handler
13    );
14    private static readonly DelegateEventAdapter<EventHandler, (object?, EventArgs), Camera> LightingChangedAdapter_0 = new(
15static (handler, ref args, me) => me.LightingChanged_Invoke_SafeEvent(handler, ref args),
16static b => (sender, e) => b.Invoke((sender, e)),
17static (handler, me) => me.LightingChanged_SafeEvent += handler,
18static (handler, me) => me.LightingChanged_SafeEvent -= handler
19);
20    private EventHandler? _lightingChanged;
21
22    private event EventHandler? _focusChanged;
23
24    private volatile EventBroker<EventHandler, (object?, EventArgs), Camera>? _focusChangedBroker;
25
26    // Field-like event.
27    [SafeEvent]
28    public event EventHandler? FocusChanged
29    {
30        add
31        {
32            EventBroker.EnsureInitialized(ref this._focusChangedBroker, FocusChangedAdapter_0, this);
33            this._focusChangedBroker.AddHandler(value);
34        }
35
36        remove
37        {
38            this._focusChangedBroker?.RemoveHandler(value);
39        }
40    }
41
42    private event EventHandler? FocusChanged_SafeEvent
43    {
44        add
45        {
46            this._focusChanged += value;
47        }
48
49        remove
50        {
51            this._focusChanged -= value;
52        }
53    }
54
55    private void FocusChanged_Invoke_SafeEvent(EventHandler? handler, ref (object? sender, EventArgs e) args)
56    {
57        try
58        {
59            handler.Invoke(args.sender, args.e);
60            return;
61        }
62        catch (Exception e)
63        {
64            Console.WriteLine(e);
65            _focusChanged -= handler;
66            return;
67        }
68    }
69    private void OnFocusChanged()
70    {
71        this._focusChanged?.Invoke(this, EventArgs.Empty);
72    }
73
74    private volatile EventBroker<EventHandler, (object?, EventArgs), Camera>? _lightingChangedBroker;
75
76    // Explicitly-implemented event.
77    [SafeEvent]
78    public event EventHandler? LightingChanged
79    {
80        add
81        {
82            EventBroker.EnsureInitialized(ref this._lightingChangedBroker, LightingChangedAdapter_0, this);
83            this._lightingChangedBroker.AddHandler(value);
84        }
85        remove
86        {
87            this._lightingChangedBroker?.RemoveHandler(value);
88        }
89    }
90
91    private event EventHandler? LightingChanged_Source { add => this._lightingChanged += value; remove => this._lightingChanged -= value; }
92
93    private event EventHandler? LightingChanged_SafeEvent
94    {
95        add
96        {
97            this.LightingChanged_Source += value;
98        }
99
100        remove
101        {
102            this.LightingChanged_Source -= value;
103        }
104    }
105
106    private void LightingChanged_Invoke_SafeEvent(EventHandler? handler, ref (object? sender, EventArgs e) args)
107    {
108        try
109        {
110            handler.Invoke(args.sender, args.e);
111            return;
112        }
113        catch (Exception e)
114        {
115            Console.WriteLine(e);
116            LightingChanged_Source -= handler;
117            return;
118        }
119    }
120    private void OnLightingChanged()
121    {
122        this._lightingChanged?.Invoke(this, EventArgs.Empty);
123    }
124}
125

The implementation pattern for event invoke operations is more complex than for other advice kinds, as explained below.

Implementation

Overriding the invoke operation requires a complex code transformation from the Metalama framework. Since the C# language has no standard way to raise an event, the only reliable way to intercept an event invocation is to insert a broker between the event implementation and the event handlers. The broker is implemented by the EventBroker<TDelegate, TArgs, TState> class. Event handlers are added to the broker, and the broker is added as a client of the original implementation.

flowchart TD
    A[Client code invokes event] --> B[Event Implementation]
    B --> D[EventBroker]

    D --> OverrideInvoke1[OverrideInvoke]
    D --> OverrideInvoke2[OverrideInvoke]
    D --> OverrideInvoke3[OverrideInvoke]

     subgraph "Advice"
      OverrideInvoke1
      OverrideInvoke2
      OverrideInvoke3
    end

    OverrideInvoke1 --> E1
    OverrideInvoke2 --> E2
    OverrideInvoke3 --> E3

    subgraph "Handlers"
      E1[Handler #1]
      E2[Handler #2]
      E3[Handler #3]
    end

Performance considerations

Unlike other advice kinds, advising event invoke operations might affect run-time performance:

This overhead might affect performance for events called at a very high frequency, although high-frequency events are not a frequent use case of .NET events.