Back to Csharplang

C# Language Design Meeting for December 4th, 2023

meetings/2023/LDM-2023-12-04.md

latest5.1 KB
Original Source

C# Language Design Meeting for December 4th, 2023

Agenda

Quote(s) of the Day

  • "New syntax?" "lock(ness) // monster"
  • "Protect against Murphy, not Machiavelli."

Discussion

Lock statement pattern

https://github.com/dotnet/csharplang/issues/7104

We started today by looking at an update for locking in .NET. .NET 9 is looking at adding a new locking primitive, System.Threading.Lock, and we need to decide what, if anything, the language needs to do to react to this change. There are three main points of contention here:

  1. Should we do anything at all?
  2. If we do, do we try to protect against accidentally observing the wrong behavior?
  3. Do we support a general pattern here, or just one specific type?

To the first question, there is some question as to whether we're ok with adding a new behavior for the lock keyword. For users of C#, does lock specifically mean Monitor.Enter(), as it has for the entire lifetime of C#, or does it have a broader meaning? There are certainly other types of locks in the BCL that do not do anything special with lock today; for example, System.Threading.SpinLock will not actually do a spin lock if lock (spinLockInstance) is done, but will just call Monitor.Enter(). This may confuse some users, but it's worked this way since the type was introduced (.NET 4.0). Is System.Threading.Lock special enough that it needs separate handling? After some discussion, we think the answer is not only yes, we think it's unfortunate that SpinLock doesn't do similar. Instead of "lock calls a specific API", we think the general user intuition is "lock enters a mutual exclusion zone and helps keep me safe from races", which is a much broader intuition. We also think it would be generally unfortunate if the advice we give to users is "Don't use the built-in Lock type with the lock keyword."

Next, we looked at the immediate concerns brought up by 1: what about times when the lock is observed as an object, rather than as a Lock? The compiler cannot handle this case in codegen, so the options for dealing with it are having the runtime handle it in Monitor.Enter() somehow, or performing some static analysis to try and warn/error about when Lock is upcast to object. Having the runtime magically handle it is attractive from a language design standpoint, but unfortunately not realistic from an implementation standpoint, particularly not unless we want to penalize all the existing locking code in .NET. Therefore, we turned to looking at static analysis. One thing that comes to the top of mind here is that, while there are lots of fun corner cases (unconstrained generics, for example), most of these corners aren't likely anything that a user will actually do with a Lock. The use pattern for these types of objects is to simply store them as a class field and lock(this.lock) where necessary. Locks usually don't get passed around through generics or other such areas, as that generally violates encapsulation. So the number of bugs that errors for upcasting will prevent are likely small. That being said, we don't think we should just brush this potential risk off as being not realistic either. Instead, we think that the compiler should perform some amount of static analysis, and warn where possible; this will catch most potential issues, but leave an escape hatch for when users really want to do something more clever.

Finally, we thought about generalizations. As mentioned earlier, we think it's unfortunate types like SpinLock aren't able to participate in lock as might be expected. That being said, we don't particularly like the pattern-based approach this proposal is taking. This is something that could be an interface with a type parameter, except for the fact that the Scope type is a ref struct and cannot go in an interface. Given that we are investigating allowing ref structs into generics, and that the runtime doesn't plan on changing anything but System.Threading.Lock for C# 13/.NET 9, we think we should just special case the new type for now, and look at a broader pattern later when we have more use cases.

Conclusion

We generally accept the changes for special casing how System.Threading.Lock interacts with the lock keyword, but we will not adopt the full pattern at this time. We will look at static analysis warnings to help make sure that accidental missuse of the type does not occur.

BinaryCompatOnlyAttribute

https://github.com/dotnet/csharplang/issues/7706
https://github.com/dotnet/csharplang/pull/7707

Finally today, we took a (very) brief look at this proposal. There was strong support from LDM for a general feature, and the BCL also would make heavy usage of this if it was added. There are still plenty of open questions to be answered (and indeed, we need to answer if this is quite the right feature, or if the feature we really want is a knob to control overload resolution at a finer grain), but we think we should take those questions on in the near term.

Conclusion

Feature moves into the working set.