HangfireIO/Hangfire · error · NotSupportedException
Anonymous functions, delegates and lambda expressions aren't
Error message
Anonymous functions, delegates and lambda expressions aren't supported in job method parameters: it's very hard to serialize them and all their scope in general.
What it means
Hangfire serializes job method arguments into persistent storage so they survive process restarts and run on potentially different servers. A parameter whose type derives from Delegate or Expression captures closure scope (local variables, captured state) that cannot be reliably serialized and deserialized. The validation at Job.cs:532 rejects any such parameter type via IsSubclassOf checks on Delegate and Expression.
Source
Thrown at src/Hangfire.Core/Common/Job.cs:534
// passed by reference are not supported.
if (parameter.IsOut)
{
throw new NotSupportedException(
"Output parameters are not supported: there is no guarantee that specified method will be invoked inside the same process.");
}
if (parameter.ParameterType.IsByRef)
{
throw new NotSupportedException(
"Parameters, passed by reference, are not supported: there is no guarantee that specified method will be invoked inside the same process.");
}
var parameterTypeInfo = parameter.ParameterType.GetTypeInfo();
if (parameterTypeInfo.IsSubclassOf(typeof(Delegate)) || parameterTypeInfo.IsSubclassOf(typeof(Expression)))
{
throw new NotSupportedException(
"Anonymous functions, delegates and lambda expressions aren't supported in job method parameters: it's very hard to serialize them and all their scope in general.");
}
}
}
private static object[] GetExpressionValues(ReadOnlyCollection<Expression> expressions)
{
var result = expressions.Count > 0 ? new object[expressions.Count] : [];
var index = 0;
foreach (var expression in expressions)
{
result[index++] = GetExpressionValue(expression);
}
return result;
}
View on GitHub (pinned to c236dd0f93)
Solutions
- Replace the delegate parameter with serializable data: pass a string enum or type name and resolve the actual callback inside the job method body.
- Split the workflow into two jobs and chain them with Continuations (BackgroundJob.ContinueJobWith) instead of passing a callback.
- Store the callback selection as a serializable argument (e.g. an enum, an int id, a DTO) and look up the implementation via a factory/registry inside the method.
- If the callback is truly needed, keep the delegate in-process and enqueue only the data-driven portion.
Example fix
// before BackgroundJob.Enqueue(() => svc.Process(orderId, result => notifier.Send(result))); // after BackgroundJob.Enqueue(() => svc.Process(orderId, NotifyChannel.Email)); // svc.Process resolves the handler from the enum value internally
Defensive patterns
Strategy: validation
Validate before calling
// Before enqueueing, verify no delegate/expression parameters exist
static bool HasUnserializableParameters(Expression<Action> jobCall)
{
var body = jobCall.Body as MethodCallExpression;
if (body == null) return false;
return body.Method.GetParameters().Any(p =>
typeof(Delegate).IsAssignableFrom(p.ParameterType) ||
typeof(System.Linq.Expressions.Expression).IsAssignableFrom(p.ParameterType));
}
if (HasUnserializableParameters(() => svc.Run(data, x => x.Transform())))
throw new InvalidOperationException("Refactor to remove delegate parameters."); Type guard
// Type guard for parameter types
static bool IsJobSafeType(Type t) =>
!typeof(Delegate).IsAssignableFrom(t) &&
!typeof(System.Linq.Expressions.Expression).IsAssignableFrom(t) &&
!t.IsByRef; Try / catch
// Not recommended — fix the job signature instead. // NotSupportedException here is a design error, not a transient failure.
Prevention
- Never pass delegates, Actions, Funcs, or lambdas as job method parameters.
- Review method signatures before enqueueing — all parameters must be JSON-serializable.
- Use continuations (ContinueJobWith) instead of callbacks.
- Replace strategy/callback parameters with serializable identifiers resolved inside the job body.
When it happens
Trigger: Enqueueing or scheduling a background job whose target method signature includes a parameter of type Action, Func<T>, Predicate<T>, Expression<...>, or any custom delegate subtype. E.g. BackgroundJob.Enqueue(() => svc.Run(data, x => x.Transform())).
Common situations: Migrating synchronous code that passed callbacks into a background job; refactoring an in-process pipeline so a handler delegate becomes a job parameter; using a generic method that accepts a Func<T> as a strategy selector.
Related errors
- Background job creation failed. See inner exception for deta
- Output parameters are not supported: there is no guarantee t
- Parameters, passed by reference, are not supported: there is
- MonitoringApi should inherit the `JobStorageMonitor` class
- exception
AI-assisted analysis of HangfireIO/Hangfire@c236dd0f93 (2026-08-13).
Data as JSON: /api/errors/0f5b61d602d70129.
Report an issue: GitHub.