public CompetitionClassModel ReplaceModel([NotNull] CompetitionClassModel replacementVersion,
            [NotNull] CompetitionClassModel originalVersion)
        {
            Guard.NotNull(replacementVersion, nameof(replacementVersion));
            Guard.NotNull(originalVersion, nameof(originalVersion));

            using (var lockTracker = new LockTracker(Log, MethodBase.GetCurrentMethod()))
            {
                lock (stateLock)
                {
                    lockTracker.Acquired();

                    if (originalVersion != activeModel.Value)
                    {
                        // Should never get here.
                        throw new OptimisticConcurrencyException("Unexpected model update from multiple threads.");
                    }

                    try
                    {
                        serializer.Save(replacementVersion);
                    }
                    catch (Exception ex)
                    {
                        Log.Error("Failed to save in-memory model to disk.", ex);
                    }

                    activeModel.Value = replacementVersion;
                    return replacementVersion;
                }
            }
        }
        private void ExecuteExclusiveIfStateIn([NotNull] ICollection<CompetitionClassState> statesAllowed,
            [NotNull] Action action)
        {
            using (var lockTracker = new LockTracker(Log, MethodBase.GetCurrentMethod()))
            {
                lock (stateLock)
                {
                    lockTracker.Acquired();

                    if (statesAllowed.Contains(classState))
                    {
                        action();
                    }
                    else
                    {
                        string statesList = string.Join(", ", statesAllowed.Select(s => s.ToString()));
                        Log.Debug(
                            $"Discarding operation, because current state {classState} is not in allowed set ({statesList}).");
                    }
                }
            }
        }
        public void Reset()
        {
            // Note: Intentionally not raising any events here, because caller wants to 
            // combine multiple changes in single network packet (performance optimization).

            StopMonitorCourseTime();

            using (var lockTracker = new LockTracker(Log, MethodBase.GetCurrentMethod()))
            {
                lock (stateLock)
                {
                    lockTracker.Acquired();

                    maximumCourseTimeElapsed = false;
                    isManuallyEliminated = false;
                    refusalCount = 0;
                }
            }
        }
        private void RaiseEventsOnChangeWithLock([NotNull] Action action)
        {
            EliminationEventArgs argsForEliminationChanged;
            EventArgs<int> argsForRefusalCountChanged;

            using (var lockTracker = new LockTracker(Log, MethodBase.GetCurrentMethod()))
            {
                lock (stateLock)
                {
                    lockTracker.Acquired();

                    bool beforeIsEliminated = UnsafeIsEliminated;
                    int beforeRefusalCount = refusalCount;

                    action();

                    argsForEliminationChanged = UnsafeIsEliminated != beforeIsEliminated
                        ? new EliminationEventArgs(UnsafeIsEliminated)
                        : null;
                    argsForRefusalCountChanged = refusalCount != beforeRefusalCount
                        ? new EventArgs<int>(refusalCount)
                        : null;
                }
            }

            if (argsForEliminationChanged != null)
            {
                EliminationChanged?.Invoke(this, argsForEliminationChanged);
            }
            if (argsForRefusalCountChanged != null)
            {
                RefusalCountChanged?.Invoke(this, argsForRefusalCountChanged);
            }
        }
        private void ExclusiveUpdateWithRaiseEvent(bool forceChanged,
            [NotNull] Func<NetworkHealthReport, NetworkHealthReport> updateCallback)
        {
            EventArgs<NetworkHealthReport> eventArgs;

            // Must lock despite of FreshReference, to prevent concurrent calls overwriting each others changes.
            using (var lockTracker = new LockTracker(Log, MethodBase.GetCurrentMethod()))
            {
                lock (StateLock)
                {
                    lockTracker.Acquired();

                    NetworkHealthReport newReport = updateCallback(previousReport.Value);

                    bool hasChanged = forceChanged || newReport != previousReport.Value;

                    previousReport.Value = newReport;

                    eventArgs = hasChanged ? new EventArgs<NetworkHealthReport>(newReport) : null;
                }
            }

            if (eventArgs != null)
            {
                Log.Info($"Network health has changed - {eventArgs.Argument}");
                HealthChanged?.Invoke(this, eventArgs);
            }
        }