dotnet/BenchmarkDotNet · error · InvalidOperationException

The current object {this} is frozen. Create a copy to modify

Error message

The current object {this} is frozen. Create a copy to modify.

What it means

CharacteristicObject is an immutable-on-freeze tree: once Frozen is true (propagated through the Owner chain), no mutations are permitted. AssertNotFrozen guards every setter, so attempting to change a value on a Job/config that has already been frozen raises this. Freezing happens when a CharacteristicObject is attached as a child of another or when the root is explicitly frozen for use.

Source

Thrown at src/BenchmarkDotNet/Characteristics/CharacteristicObject.cs:60

            sharedValues = [];
        }

        protected CharacteristicObject(string id) : this()
        {
            if (id.IsNotBlank())
            {
                IdCharacteristic[this] = id;
            }
        }
        #endregion

        #region Assertions

        private void AssertNotFrozen()
        {
            if (Frozen)
            {
                throw new InvalidOperationException($"The current object {this} is frozen. Create a copy to modify.");
            }
        }

        private void AssertIsRoot()
        {
            if (Owner != null)
            {
                throw new InvalidOperationException(
                    "The current operation allowed for root nodes only, " +
                    $"but the value {this} is attached to another node, {Owner}.");
            }
        }

        private void AssertIsNonFrozenRoot()
        {
            AssertNotFrozen();
            AssertIsRoot();
        }

View on GitHub (pinned to b515068b61)

Solutions

  1. Create a copy before modifying: BDN APIs return new unfrozen instances from With*() builder methods, so chain those instead of mutating in place.
  2. Capture the Job before it is added to a Config and only modify that pre-freeze reference.
  3. Never mutate shared/default instances (Job.Default, default configs); derive a new one from them.
  4. If you hold a frozen object you must change, clone it via its copy/build API first.

Example fix

// before - 'job' was already added to a frozen Config
job.WithRuntime(newRuntime); // throws

// after - builder methods produce a fresh, unfrozen instance
var job = originalJob.WithRuntime(newRuntime);
Defensive patterns

Strategy: validation

Validate before calling

if (job.Frozen)
    job = job.WithRuntime(newRuntime); // builder returns a fresh unfrozen instance
else
    job.Runtime = newRuntime;

Type guard

static bool IsMutable(CharacteristicObject o) => !o.Frozen;

Try / catch

try { obj.SomeSetter(value); }
catch (InvalidOperationException ex) when (ex.Message.Contains("frozen"))
{
    var copy = obj.Clone(); // or rebuild via With*
    copy.SomeSetter(value);
}

Prevention

When it happens

Trigger: Mutating a Job instance after it has been added to a Config and the Config/runner has frozen it, editing the shared DefaultJobInstance or a resolver-provided template, or reusing a benchmark case's Job object to apply another With(...) modification.

Common situations: Calling job.WithRuntime(...) on a Job pulled from an already-built Config, sharing a static Job field across runs and modifying it between runs, or programmatically cloning a config and editing the original.

Related errors


AI-assisted analysis of dotnet/BenchmarkDotNet@b515068b61 (2026-08-13). Data as JSON: /api/errors/cce8776858f8322e. Report an issue: GitHub.