HangfireIO/Hangfire · error · InvalidOperationException

Expression object should be not null.

Error message

Expression object should be not null.

What it means

InvalidOperationException thrown by Job.FromExpression when the expression's instance object (callExpression.Object) evaluates to null at job-creation time. When you enqueue an instance method via a closure (e.g., () => myService.DoWork()), Hangfire evaluates the captured object to infer its concrete runtime type — the object is used only for type discovery, not serialized. A null instance means the target's type cannot be determined, so Hangfire refuses to build the job. Note this only applies when no explicit TType is provided (the typed overloads FromExpression<T> bypass this path).

Source

Thrown at src/Hangfire.Core/Common/Job.cs:444

            {
                throw new ArgumentException("Expression body should be of type `MethodCallExpression`", nameof(methodCall));
            }

            var type = explicitType ?? callExpression.Method.DeclaringType;
            var method = callExpression.Method;

            if (explicitType == null && callExpression.Object != null)
            {
                // Creating a job that is based on a scope variable. We should infer its
                // type and method based on its value, and not from the expression tree.

                // TODO: BREAKING: Consider removing this special case entirely.
                // People consider that the whole object is serialized, this is not true.

                var objectValue = GetExpressionValue(callExpression.Object);
                if (objectValue == null)
                {
                    throw new InvalidOperationException("Expression object should be not null.");
                }

                // TODO: BREAKING: Consider using `callExpression.Object.Type` expression instead.
                type = objectValue.GetType();

                // If an expression tree is based on interface, we should use its own
                // MethodInfo instance, based on the same method name and parameter types.
                method = type.GetNonOpenMatchingMethod(
                    callExpression.Method.Name,
                    callExpression.Method.GetParameters().Select(static x => x.ParameterType).ToArray());
            }

            return new Job(
                // ReSharper disable once AssignNullToNotNullAttribute
                type,
                method,
                GetExpressionValues(callExpression.Arguments),
                queue);

View on GitHub (pinned to c236dd0f93)

Solutions

  1. Use the typed overload BackgroundJob.Enqueue<TService>(x => x.DoWork()) so Hangfire uses typeof(TService) and does not evaluate the instance.
  2. Ensure the captured instance is non-null before enqueueing — validate DI registrations and initialization order.
  3. For interface-based services, register the concrete type in DI and use the typed overload to avoid runtime evaluation entirely.

Example fix

// before — instance may be null
var service = ResolveService(); // could be null
BackgroundJob.Enqueue(() => service.DoWork());

// after — typed overload, no instance evaluation
BackgroundJob.Enqueue<IMyService>(x => x.DoWork());
Defensive patterns

Strategy: type-guard

Validate before calling

// Prefer typed overloads that avoid instance evaluation:
// BackgroundJob.Enqueue<TService>(x => x.Work())
// If using an instance closure, ensure the instance is non-null:
if (instance == null) throw new InvalidOperationException("Job target instance is null; register it in DI or use the typed overload.");

Type guard

public static bool IsInstanceResolvable(Expression<Action> expr)
    => expr.Body is MethodCallExpression mc && (mc.Object == null || GetExpressionValueSafe(mc.Object) != null);

Try / catch

try { BackgroundJob.Enqueue(() => instance.Work()); }
catch (InvalidOperationException ex) when (ex.Message.Contains("Expression object should be not null")) { /* switch to Enqueue<T> or fix DI */ }

Prevention

When it happens

Trigger: Enqueuing () => instance.Method() where instance is null at enqueue time; passing a service resolved from DI that returned null; a field/property that was not initialized before the enqueue call.

Common situations: Constructor-injected service that is null due to missing DI registration; enqueuing from a static context where the instance field is not yet set; Lazy<T> whose Value was never accessed; null returned from a factory method used inline in the lambda.

Related errors


AI-assisted analysis of HangfireIO/Hangfire@c236dd0f93 (2026-08-13). Data as JSON: /api/errors/ca5009f379015dbc. Report an issue: GitHub.