2

I'm reading Effective C# and I came across the following pattern to improve event-calling behavior and make it thread-safe:

    public class EventSource {
        private EventHandler<int> Updated;
        private int counter; 

        public void RaiseUpdates(){
          counter++;
          var handler = Updated;
          if (handler != null){
              handler(this, counter);
        }

The book claims that since there a "shallow copy" in the assignment to handler, the call handler(this, counter) will call all registered clients even if moments before one of them unsubscribed. But aren't delegates in C# reference types? Wouldn't this assignment just create a new reference for the underlying delegate object?

  • 1
    This is because `if (Updated != null) Updated(this, counter)` has a race condition. In that case Updated could be set to null after the null check but before it's invoked. – Zer0 Mar 14 '20 at 00:13
  • 1
    Emphasis: the book is out of date; you should just use `counter++; Updated?.Invoke(this, counter);` - same logic, just: less stuff to remember to do – Marc Gravell Mar 14 '20 at 00:14
  • To zer0, that part I did get. – estefano-ppt Mar 14 '20 at 00:28
  • @MarcGravell Why does the null-conditional solution work? Does the C# specification guarantee atomicity? Does it make an underlying copy of the delegate? – estefano-ppt Mar 14 '20 at 00:50
  • 1
    @estefano yes, it copies the operand (the reference in this case; not a copy of the delegate itself) – Marc Gravell Mar 14 '20 at 08:02

1 Answers1

2

The delegate object is immutable, so a copy of the reference to it is fine. A standalone local copy of a reference to an immutable object is pretty much the gold standard for avoiding a thread-race problem.

When you add/remove an event subscription, Delegate.Combine et al create a new delegate instance every time it changes (or null if you unsubscribe the last handler) and assigns a reference (/ null) to that new object to the backing field. That is why the snapshot is helpful.

BTW : in modern C#, you can just use TheEvent?.Invoke(....), which does this for you.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Brilliant. Thank oh so very much kind sir. – estefano-ppt Mar 14 '20 at 00:12
  • 1
    Reference: ECMA-334 C# Language Specification, ch. 20.5: `Once instantiated, a delegate instance always refers to the same invocation list. [Note: Remember, when two delegates are combined, or one is removed from another, a new delegate results with its own invocation list; the invocation lists of the delegates combined or removed remain unchanged. end note]` – Renat Mar 14 '20 at 00:25
  • Yes, the book does endorse this new solution, but why does.it work? Does the null-conditional operator make a copy of its operand? – estefano-ppt Mar 14 '20 at 00:26
  • 1
    @estefano-ppt yes, it does – Marc Gravell Mar 14 '20 at 08:01