dotnet/BenchmarkDotNet · error · InvalidOperationException

Type {0}: no settable property {1} found.

Error message

Type {0}: no settable property {1} found.

What it means

Thrown by the InProcessNoEmit runner's FillMembers method when injecting [Params] values onto the benchmark class instance via reflection. For each parameter it first looks up a public property with the matching name; if one is found but has no public setter (GetSetMethod() returns null), this InvalidOperationException is raised. This only occurs with the in-process no-emit toolchain (e.g. iOS, WASM, or explicitly selected), because it sets values reflectively rather than via emitted code.

Source

Thrown at src/BenchmarkDotNet/Toolchains/InProcess/NoEmit/InProcessNoEmitRunner.cs:97

                flags |= parameter.IsStatic ? BindingFlags.Static : BindingFlags.Instance;

                var paramProperty = targetType.GetProperty(parameter.Name, flags);

                if (paramProperty == null)
                {
                    var paramField = targetType.GetField(parameter.Name, flags);
                    if (paramField == null)
                        throw new InvalidOperationException(
                            $"Type {targetType.FullName}: no property or field {parameter.Name} found.");

                    var callInstance = paramField.IsStatic ? null : instance;
                    paramField.SetValue(callInstance, parameter.Value);
                }
                else
                {
                    var setter = paramProperty.GetSetMethod();
                    if (setter == null)
                        throw new InvalidOperationException(
                            $"Type {targetType.FullName}: no settable property {parameter.Name} found.");

                    var callInstance = setter.IsStatic ? null : instance;
                    setter.Invoke(callInstance, [parameter.Value]);
                }
            }

            // Inject CancellationToken into properties/fields marked with [BenchmarkCancellation]
            foreach (var property in targetType.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static))
            {
                if (property.PropertyType == typeof(CancellationToken) &&
                    property.IsDefined(typeof(Attributes.BenchmarkCancellationAttribute), inherit: false))
                {
                    var setter = property.GetSetMethod();
                    if (setter != null)
                    {
                        var callInstance = setter.IsStatic ? null : instance;
                        setter.Invoke(callInstance, [cancellationToken]);

View on GitHub (pinned to b515068b61)

Solutions

  1. Add a public setter to the parameter property: change { get; } or { get; private set; } to { get; set; }
  2. Convert the parameter from a property to a public field: public int Size; with [Params] — fields do not need setters and are handled by the separate branch at line 85
  3. If you cannot expose a public setter, switch the job away from the no-emit toolchain to the default emitting toolchain (remove .WithToolchain(InProcessNoEmitToolchain.Default)) so BDN generates setter code instead of using reflection

Example fix

// before
public class Bench
{
    [Params(1, 2, 4)]
    public int Size { get; private set; } // no public setter

    [Benchmark]
    public void Run() => _ = Size * 2;
}

// after (option 1: public setter)
public class Bench
{
    [Params(1, 2, 4)]
    public int Size { get; set; }

    [Benchmark]
    public void Run() => _ = Size * 2;
}

// after (option 2: public field)
public class Bench
{
    [Params(1, 2, 4)]
    public int Size;

    [Benchmark]
    public void Run() => _ = Size * 2;
}
Defensive patterns

Strategy: validation

Validate before calling

// Run before BenchmarkRunner.Run to verify every [Params] member has a public setter or is a field.
using System.Reflection;
using BenchmarkDotNet.Attributes;

static List<string> ValidateParams(Type benchmarkType)
{
    var problems = new List<string>();
    foreach (var prop in benchmarkType.GetProperties(
        BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static))
    {
        if (!prop.IsDefined(typeof(ParamsAttribute), inherit: true)
            && !prop.IsDefined(typeof(ParamsAllValuesAttribute), inherit: true)
            && !prop.IsDefined(typeof(ParamsSourceAttribute), inherit: true))
            continue;

        if (prop.GetSetMethod() is null)
            problems.Add($"{benchmarkType.Name}.{prop.Name}: [Params] property has no public setter; use a field or add '{{ get; set; }}'.");
    }
    return problems;
}

var errors = ValidateParams(typeof(MyBench));
if (errors.Count > 0) throw new InvalidOperationException(string.Join("\n", errors));

Type guard

// Narrow a PropertyInfo to 'has a public setter' before relying on it for [Params] injection.
static bool HasPublicSetter(PropertyInfo p) => p.GetSetMethod(nonPublic: false) is not null;

Try / catch

// This is a configuration error; catching it is a last resort. Prefer fixing the member.
try { BenchmarkRunner.Run<MyBench>(); }
catch (InvalidOperationException ex) when (ex.Message.Contains("no settable property"))
{
    // Log the offending type/property from the message and surface a clear config error.
    throw new ConfigurationErrorsException($"[Params] member is not settable in no-emit mode: {ex.Message}", ex);
}

Prevention

When it happens

Trigger: FillMembers iterates benchmarkCase.Parameters.Items and calls targetType.GetProperty(parameter.Name, BindingFlags.Public | Static-or-Instance). When a property is found, it calls paramProperty.GetSetMethod() (the no-arg overload, which returns only a PUBLIC setter). If that returns null, the exception fires. This means: (a) [Params] on a read-only auto-property { get; }, (b) [Params] on a property with a private/protected/internal setter like { get; private set; }, (c) [Params] on an expression-bodied property => ....

Common situations: Declaring [Params(1,2,3)] public int Size { get; private set; } (encapsulation habit that breaks no-emit mode). Switching a benchmark from the default emit toolchain to InProcessNoEmitToolchain and discovering readonly params. Running benchmarks on iOS (which forces InProcessNoEmit) where the same benchmark worked on desktop.

Related errors


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