Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt

Use this file to discover all available pages before exploring further.

A custom event accessor is an explicit implementation of the add and remove blocks within an event declaration. By defining these accessors, a developer overrides the C# compiler’s default behavior of automatically generating a private backing delegate field and the associated thread-safe subscription logic. When custom accessors are declared, the event behaves syntactically like a property with get and set accessors. The add block is invoked when a subscriber uses the += operator, and the remove block is invoked with the -= operator. Both blocks receive an implicit parameter named value (typed to match the event’s delegate type), which represents the delegate instance being subscribed or unsubscribed.

Syntax Structure

public event EventHandler MyCustomEvent
{
    add
    {
        // Logic to store the 'value' delegate
    }
    remove
    {
        // Logic to remove the 'value' delegate
    }
}

Internal Mechanics and State Management

When you omit custom accessors (creating a “field-like event”), the C# compiler automatically generates a private delegate field and uses Interlocked.CompareExchange to ensure thread-safe updates to the delegate chain. When you implement custom accessors, the compiler generates no backing field. You are strictly responsible for:
  1. Storage: Declaring a data structure or backing field to hold the delegate(s).
  2. Combination/Removal: Using Delegate.Combine and Delegate.Remove to manage the multicast delegate chain.
  3. Thread Safety: Implementing synchronization (e.g., using lock or Interlocked operations) if the event will be accessed concurrently.

Standard Implementation Example

The following demonstrates the mechanical implementation of a custom event accessor using a manual backing field and a synchronization object to ensure thread safety.
public class EventPublisher
{
    // 1. Manual backing storage
    private EventHandler _myCustomEvent;
    
    // 2. Synchronization primitive
    private readonly object _eventLock = new object();

    public event EventHandler MyCustomEvent
    {
        add
        {
            lock (_eventLock)
            {
                // Combine the incoming delegate ('value') with the existing chain
                _myCustomEvent = (EventHandler)Delegate.Combine(_myCustomEvent, value);
            }
        }
        remove
        {
            lock (_eventLock)
            {
                // Remove the incoming delegate ('value') from the existing chain
                _myCustomEvent = (EventHandler)Delegate.Remove(_myCustomEvent, value);
            }
        }
    }

    protected virtual void OnMyCustomEvent(EventArgs e)
    {
        EventHandler handler;
        
        // Read the delegate chain safely
        lock (_eventLock)
        {
            handler = _myCustomEvent;
        }
        
        // Invoke the delegate chain outside the lock to prevent deadlocks
        handler?.Invoke(this, e);
    }
}

Compilation Translation

At the Common Intermediate Language (CIL) level, the C# compiler translates the add and remove blocks into two distinct methods, typically prefixed with add_ and remove_. For the event MyCustomEvent, the compiler generates:
  • public void add_MyCustomEvent(EventHandler value)
  • public void remove_MyCustomEvent(EventHandler value)
The event keyword itself merely emits metadata linking these two methods as the mutators for the event. This metadata allows the CLR, reflection, and external assemblies to recognize the methods as the standard subscription interface for that specific event.
Master C# with Deep Grasping Methodology!Learn More