pardeike/Harmony · error · ArgumentException
Interface methods must be called virtually
Error message
Interface methods must be called virtually
What it means
AccessTools.MethodDelegate<DelegateType> throws this ArgumentException when the method being wrapped is declared on an interface but the caller explicitly passed virtualCall: false. Interface methods have no concrete body, so a direct (non-virtual) call to them is meaningless and Harmony refuses to create such a delegate.
Solutions
- Pass virtualCall: true so the call is dispatched virtually to the implementing object.
- Resolve the concrete implementation method from the implementing class instead of the interface.
- If you truly need a non-virtual call, target a non-interface declaring type.
Example fix
// before
var d = AccessTools.MethodDelegate<Func<int>>(typeof(IMover).GetMethod("Step"), mover, false);
// after
var d = AccessTools.MethodDelegate<Func<int>>(typeof(IMover).GetMethod("Step"), mover); // virtualCall defaults to true Defensive patterns
Strategy: validation
Validate before calling
if (method.DeclaringType?.IsInterface == true && !virtualCall)
throw new InvalidOperationException("Interface methods require virtualCall: true"); Type guard
bool IsNonVirtualInterfaceCall(MethodInfo m, bool virtualCall) => m.DeclaringType?.IsInterface == true && !virtualCall;
Try / catch
try { var d = AccessTools.MethodDelegate<T>(method, instance, nonVirtual); }
catch (ArgumentException) when (method.DeclaringType?.IsInterface == true) { d = AccessTools.MethodDelegate<T>(method, instance); } Prevention
- Never pass virtualCall: false for interface-declared methods.
- Resolve concrete implementation methods when you need non-virtual dispatch.
- Prefer default overload (virtual) unless you know the method is non-virtual.
When it happens
Trigger: AccessTools.MethodDelegate<T>(interfaceMethod, instance, nonVirtualCall: false) - i.e. virtualCall parameter false - where method.DeclaringType.IsInterface is true.
Common situations: Wrapping a method obtained from typeof(IMyInterface).GetMethod(...) while disabling virtual dispatch, often to call a base/default implementation directly.
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
- Invalid delegate type
- Delegate has no defined original method
- The type must declare an empty constructor (the constructor…
- Value cannot be null. (Parameter 'fromMethod')
- Value cannot be null. (Parameter 'method')
AI-assisted analysis of pardeike/Harmony@e7872dc170 (2026-09-15).
Data as JSON: /api/errors/1a2e7e65b4a6a492.
Report an issue: GitHub.
Appendix: source
Thrown at Harmony/Tools/AccessTools.cs:1805
/// </remarks>
///
public static DelegateType MethodDelegate<DelegateType>(MethodInfo method, object instance = null, bool virtualCall = true, Type[] delegateArgs = null) where DelegateType : Delegate
{
if (method is null)
throw new ArgumentNullException(nameof(method));
var delegateType = typeof(DelegateType);
// Static method delegate
if (method.IsStatic)
{
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)
{
View on GitHub (pinned to e7872dc170)