{"record":{"id":"0f5b61d602d70129","repo":"HangfireIO/Hangfire","slug":"anonymous-functions-delegates-and-lambda-expressi","errorCode":null,"errorMessage":"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.","messagePattern":"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\\.","errorType":"exception","errorClass":"NotSupportedException","httpStatus":null,"severity":"error","filePath":"src/Hangfire.Core/Common/Job.cs","lineNumber":534,"sourceCode":"                // passed by reference are not supported.\n\n                if (parameter.IsOut)\n                {\n                    throw new NotSupportedException(\n                        \"Output parameters are not supported: there is no guarantee that specified method will be invoked inside the same process.\");\n                }\n\n                if (parameter.ParameterType.IsByRef)\n                {\n                    throw new NotSupportedException(\n                        \"Parameters, passed by reference, are not supported: there is no guarantee that specified method will be invoked inside the same process.\");\n                }\n\n                var parameterTypeInfo = parameter.ParameterType.GetTypeInfo();\n                \n                if (parameterTypeInfo.IsSubclassOf(typeof(Delegate)) || parameterTypeInfo.IsSubclassOf(typeof(Expression)))\n                {\n                    throw new NotSupportedException(\n                        \"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.\");\n                }\n            }\n        }\n\n        private static object[] GetExpressionValues(ReadOnlyCollection<Expression> expressions)\n        {\n            var result = expressions.Count > 0 ? new object[expressions.Count] : [];\n            var index = 0;\n\n            foreach (var expression in expressions)\n            {\n                result[index++] = GetExpressionValue(expression);\n            }\n\n            return result;\n        }\n","sourceCodeStart":516,"sourceCodeEnd":552,"githubUrl":"https://github.com/HangfireIO/Hangfire/blob/c236dd0f930f831ec151e436e138ddc429a02a72/src/Hangfire.Core/Common/Job.cs#L516-L552","documentation":"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.","triggerScenarios":"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())).","commonSituations":"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.","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."],"exampleFix":"// before\nBackgroundJob.Enqueue(() => svc.Process(orderId, result => notifier.Send(result)));\n\n// after\nBackgroundJob.Enqueue(() => svc.Process(orderId, NotifyChannel.Email));\n// svc.Process resolves the handler from the enum value internally","handlingStrategy":"validation","validationCode":"// Before enqueueing, verify no delegate/expression parameters exist\nstatic bool HasUnserializableParameters(Expression<Action> jobCall)\n{\n    var body = jobCall.Body as MethodCallExpression;\n    if (body == null) return false;\n    return body.Method.GetParameters().Any(p =>\n        typeof(Delegate).IsAssignableFrom(p.ParameterType) ||\n        typeof(System.Linq.Expressions.Expression).IsAssignableFrom(p.ParameterType));\n}\n\nif (HasUnserializableParameters(() => svc.Run(data, x => x.Transform())))\n    throw new InvalidOperationException(\"Refactor to remove delegate parameters.\");","typeGuard":"// Type guard for parameter types\nstatic bool IsJobSafeType(Type t) =>\n    !typeof(Delegate).IsAssignableFrom(t) &&\n    !typeof(System.Linq.Expressions.Expression).IsAssignableFrom(t) &&\n    !t.IsByRef;","tryCatchPattern":"// Not recommended — fix the job signature instead.\n// NotSupportedException here is a design error, not a transient failure.","preventionTips":["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."],"tags":["serialization","delegates","job-arguments","notsupported"],"backgroundTag":null,"analyzedSha":"c236dd0f930f831ec151e436e138ddc429a02a72","analyzedAt":"2026-08-13T20:27:11.027Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}