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
- Add a public setter to the parameter property: change { get; } or { get; private set; } to { get; set; }
- 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
- 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
- Prefer public fields over properties for [Params] — fields never need setters and work across all toolchains.
- If you must use a property, always declare it as { get; set; }, never { get; } or { get; private set; }.
- When targeting iOS/WASM/Android (which force InProcessNoEmit), run the validation helper above in a unit test over every benchmark class.
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
- The argument must be an array
- Please use Targets property
- Field 'ConsoleRunnerPackage' not found.
- Unable to get value of 'ConsoleRunnerPackage'.
- Method 'GetRunnerPath' not found.
AI-assisted analysis of dotnet/BenchmarkDotNet@b515068b61 (2026-08-13).
Data as JSON: /api/errors/954eb5e704e33915.
Report an issue: GitHub.