17

From the MSDN documentation for EventHandler Delegate:

In contrast to the C# and Visual Basic examples, the Visual C++ example code does not require you to create a thread-safe temporary variable. Visual C++ version automatically provides thread-safe access, enabling you to raise the event directly.

Why can C# not automatically provide thread-safe access to events, where C++/CLI can?

James McNellis
  • 348,265
  • 75
  • 913
  • 977
user310291
  • 36,946
  • 82
  • 271
  • 487
  • I've updated your question to refer specifically to C++/CLI: the "Visual C++" name encompasses multiple languages (including C, C++, and C++/CLI), and the language intended in the cited MSDN text is C++/CLI. – James McNellis Mar 07 '11 at 04:37
  • 1
    While the question isn't (exactly) subjective and argumentative, I can't quite figure out a way to phrase an answer accurately while avoiding sounding like an outright condemnation of C# as a technical failure. – Jerry Coffin Mar 07 '11 at 04:42
  • @James well, but the MSDN article *does* use the word "Visual C++" to refer to the examples. – Yuji Mar 07 '11 at 04:44
  • 1
    @Yuji: yes, but it's not really accurate -- a conforming implementation of C++ *must* reject the code in the article. – Jerry Coffin Mar 07 '11 at 04:45
  • @Jerry: C# has different priorities, like cool new LINQy stuff and dynamic typing scenarios. Making thread-safety easy is obviously not a C# priority, just like making deterministic destruction of members easy is not a C# priority. C++/CLI (and direct access to the Win32 API) tends to solve the problems well that C++ and the Win32 API have always solved well (templates with duck typing, RAII, destructors that implicitly call destructors of members, asynchronous I/O using the overlapped model instead of unnecessary worker threads). – Ben Voigt Mar 07 '11 at 04:47
  • 1
    @Ben: I can't help thinking of LINQ as Microsoft's Maginot Line. Somewhere at Microsoft its designer rubs his hands in glee and proclaims: "LINQ will destroy PowerBuilder once and for all!". Nobody's had the heart to tell him that PowerBuilder was essentially dead and gone before LINQ arrived... – Jerry Coffin Mar 07 '11 at 05:08
  • @Jerry: Right -- I see lots of people using the extension methods, but no one seems to use LINQ syntax. [And SQL-style syntax has (had) long been available for people who wanted it](http://en.wikipedia.org/wiki/Embedded_SQL). – Ben Voigt Mar 07 '11 at 05:14
  • @Jerry: Yes I agree with that. Presumably it serves Microsoft's purpose to use confusing terminology on C++ on Microsoft's websites... Oh my. – Yuji Mar 07 '11 at 05:45
  • @Yuji: It's nothing new really. For decades vendors (and *not* just MS) have used standards for basically bait and switch tactics. They may preach standardization and portability -- but they do their best to make extensions "compelling" enough to prevent exactly that. – Jerry Coffin Mar 07 '11 at 05:50
  • 1
    Besides everything everyone else has already mentioned, if you look at the VB.Net sample, you'll also see no use of a thread-safe temporary variable - the documentation is wrong, VB provides the same thread safety automatically that C++ does. – Damien_The_Unbeliever Mar 07 '11 at 08:05
  • @Damien do you mean VB.NET can provide thread safety and not C# in this use case ? – user310291 Mar 11 '11 at 19:54
  • @user310291 - yes, the VB.Net compiler produces thread-safe code for raising events. From C++, C# and VB, C# is the odd one out in this regard. – Damien_The_Unbeliever Mar 11 '11 at 20:35

2 Answers2

3

This post is quite relevant for background info. An event has three accessors: add, remove and raise. Respectively to add an event handler, remove it and to raise the event. The compiler auto-generates one when you don't write an accessor explicitly.

The C++/CLI compiler auto-generates the raise accessor if you don't write one. It uses the pattern you see in C# code with the helper variable that avoids the null reference exception. Just as you see it in the linked post. For some mysterious reason the C# language doesn't do this. It doesn't even let you define your own accessor, you have to raise the event yourself. Forcing you to write code with the helper variable.

I have no clue why the C# team made this decision. As a rule, the team strongly favors avoiding auto-generated code that slows down execution. The C++ principle of 'you don't pay for what you don't use'. There are certainly many cases where the thread-safety is unnecessary, any events in GUI code for example. This is a heck of an edge-case though, given the low cost and the fact that GUI code contains the pattern anyway. Low-level locking in C++ libraries is however common.

Community
  • 1
  • 1
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • 1
    The "raise" accessor within the event descriptor is vestigial, and is not actually used when raising events. The fundamental problem in C# is that defining an event `Foo` creates a private field of the same name, rather than e.g. `Foo_event`, and things which should be perceived as operations with an event are instead seen as actions on the delegate. The language would have been cleaner if `Foo` were an event name, which supported add, remove, invoke-if-not-null, test-against-null, and set-to-null operations, and if doing anything else required explicit reference to the delegate. – supercat Oct 22 '12 at 18:48
1

While not exactly a duplicate, I believe the answers in this question will help explain why they aren't thread safe in terms of implementation. Specifically these two answers.

Marc Gravell's answer:

IMO, the other answers miss one key detail - that delegates (and therefore events) are immutable. The significance of this is that subscribing or unsubscribing an event handler doesn't simply append/remove to a list - rather, it replaces the list with a new one with an extra (or one less) item on it.

Since references are atomic, this means that at the point you do:

var handler = SomeEvent;

you now have a rigid instance that cannot change, even if in the next picosecond another thread unsubscribes (causing the actual event field to become null).

So you test for null and invoke it, and all is well. Note of course that there is still the confusing scenario of the event being raised on an object that thinks it unsubscribed a picosecond ago!

Community
  • 1
  • 1
Jeff Swensen
  • 3,513
  • 28
  • 52