Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 55 additions & 1 deletion src/DynamicData/Internal/SwappableLock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,73 @@ public static SwappableLock CreateAndEnter(object gate)
return result;
}

#if NET9_0_OR_GREATER
public static SwappableLock CreateAndEnter(Lock gate)
{
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On NET9+, the Lock overloads can still receive null at runtime (despite non-nullable annotations). Currently that would throw a NullReferenceException from gate.Enter()/Exit(), unlike the object overloads which throw ArgumentNullException. Consider adding an explicit null guard (e.g., ArgumentNullException.ThrowIfNull(gate)) in CreateAndEnter(Lock) to provide a consistent and clearer failure mode.

Suggested change
{
{
System.ArgumentNullException.ThrowIfNull(gate);

Copilot uses AI. Check for mistakes.
gate.Enter();
return new SwappableLock() { _lockGate = gate };
}
#endif

public void SwapTo(object gate)
{
#if NET9_0_OR_GREATER
if (_gate is null && _lockGate is null)
throw new InvalidOperationException("Lock is not initialized");
#else
if (_gate is null)
throw new InvalidOperationException("Lock is not initialized");
#endif

var hasNewLock = false;
Monitor.Enter(gate, ref hasNewLock);

#if NET9_0_OR_GREATER
if (_lockGate is not null)
{
_lockGate.Exit();
_lockGate = null;
}
else
#endif
if (_hasLock)
Monitor.Exit(_gate);
{
Monitor.Exit(_gate!);
}

_hasLock = hasNewLock;
_gate = gate;
}

#if NET9_0_OR_GREATER
public void SwapTo(Lock gate)
{
if (_lockGate is null && _gate is null)
throw new InvalidOperationException("Lock is not initialized");

gate.Enter();

Comment on lines +60 to +66
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SwapTo(Lock gate) should guard against a null Lock argument as well; otherwise gate.Enter() will throw NullReferenceException. Adding an explicit ArgumentNullException for a null gate keeps behavior consistent with Monitor.Enter on the object overloads and produces a more actionable exception.

Copilot uses AI. Check for mistakes.
if (_lockGate is not null)
_lockGate.Exit();
else if (_hasLock)
Monitor.Exit(_gate!);

_lockGate = gate;
_hasLock = false;
_gate = null;
}
Comment on lines +21 to +75
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR adds new NET9+ behavior (CreateAndEnter(Lock), SwapTo(Lock), and mixed-type swapping/release logic) but there are no tests covering it. Since the test project targets net9.0, it should be feasible to add unit tests that validate swapping object→Lock, Lock→object, and Dispose releasing the correct gate.

Copilot uses AI. Check for mistakes.
#endif

public void Dispose()
{
#if NET9_0_OR_GREATER
if (_lockGate is not null)
{
_lockGate.Exit();
_lockGate = null;
}
else
#endif
if (_hasLock && (_gate is not null))
{
Monitor.Exit(_gate);
Expand All @@ -45,4 +95,8 @@ public void Dispose()

private bool _hasLock;
private object? _gate;

#if NET9_0_OR_GREATER
private Lock? _lockGate;
#endif
}
Loading