pardeike/Harmony · error · ArgumentException

Invalid delegate type

Error message

Invalid delegate type

What it means

AccessTools.MethodDelegate throws this ArgumentException when creating an open-instance delegate whose delegate signature has no first parameter to hold the instance. Delegate.CreateDelegate should throw a proper ArgumentException in that case; Harmony adds this as a fallback in case the runtime does not throw.

Solutions

  1. Use a delegate type whose first parameter is the instance type (open-instance delegate), e.g. Func<MyClass, int> instead of Func<int>.
  2. Or pass a specific instance to MethodDelegate so a closed delegate can be created.
  3. Or wrap a static method if a parameterless delegate is intended.

Example fix

// before
var d = AccessTools.MethodDelegate<Func<int>>(getNameMethod);
// after
var d = AccessTools.MethodDelegate<Func<Entity, string>>(getNameMethod);
Defensive patterns

Strategy: type-guard

Validate before calling

var invoke = delegateType.GetMethod("Invoke");
var isInstanceMethod = !method.IsStatic;
if (instance is null && isInstanceMethod && invoke.GetParameters().Length == 0)
    throw new InvalidOperationException("Need Func<TInstance, ...> delegate or a closed instance");

Type guard

bool IsValidOpenDelegate(DelegateType d, MethodInfo m) => d.GetMethod("Invoke").GetParameters().Length == (m.IsStatic ? 0 : 1);

Try / catch

try { var d = AccessTools.MethodDelegate<T>(method); }
catch (ArgumentException) { throw new InvalidOperationException("Delegate signature must include the instance as first parameter"); }

Prevention

When it happens

Trigger: Calling AccessTools.MethodDelegate<DelegateType>(method) with instance == null where the delegate type's Invoke has zero parameters but the method is an open-instance (non-static) method requiring an instance argument.

Common situations: Using a parameterless delegate like Func<int> for an instance method instead of a delegate whose first parameter is the instance type, e.g. Func<MyClass, int>.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of pardeike/Harmony@e7872dc170 (2026-09-15). Data as JSON: /api/errors/899804528a7cff0d. Report an issue: GitHub.

Appendix: source

Thrown at Harmony/Tools/AccessTools.cs:1817

				return (DelegateType)Delegate.CreateDelegate(delegateType, method);
			}

			var declaringType = method.DeclaringType;
			if (declaringType != null && declaringType.IsInterface && !virtualCall)
			{
				throw new ArgumentException("Interface methods must be called virtually");
			}

			// Open instance method delegate ...
			if (instance is null)
			{
				var delegateParameters = delegateType.GetMethod("Invoke").GetParameters();
				if (delegateParameters.Length == 0)
				{
					// Following should throw an ArgumentException with the proper message string.
					_ = Delegate.CreateDelegate(typeof(DelegateType), method);
					// But in case it doesn't...
					throw new ArgumentException("Invalid delegate type");
				}
				var delegateInstanceType = delegateParameters[0].ParameterType;
				// Exceptional case: delegate struct instance type cannot be created from an interface method.
				// This case is handled in the "non-virtual call" case, using the struct method and the matching delegate instance type.
				if (declaringType != null && declaringType.IsInterface && delegateInstanceType.IsValueType)
				{
					var interfaceMapping = delegateInstanceType.GetInterfaceMap(declaringType);
					method = interfaceMapping.TargetMethods[Array.IndexOf(interfaceMapping.InterfaceMethods, method)];
					declaringType = delegateInstanceType;
				}

				// ... that virtually calls ...
				if (declaringType != null && virtualCall)
				{
					// ... an interface method
					// If method is already an interface method, just create a delegate from it directly.
					if (declaringType.IsInterface)
					{

View on GitHub (pinned to e7872dc170)