{"record":{"id":"1a2e7e65b4a6a492","repo":"pardeike/Harmony","slug":"interface-methods-must-be-called-virtually","errorCode":null,"errorMessage":"Interface methods must be called virtually","messagePattern":"Interface methods must be called virtually","errorType":"exception","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"Harmony/Tools/AccessTools.cs","lineNumber":1805,"sourceCode":"\t\t/// </remarks>\r\n\t\t///\r\n\t\tpublic static DelegateType MethodDelegate<DelegateType>(MethodInfo method, object instance = null, bool virtualCall = true, Type[] delegateArgs = null) where DelegateType : Delegate\r\n\t\t{\r\n\t\t\tif (method is null)\r\n\t\t\t\tthrow new ArgumentNullException(nameof(method));\r\n\r\n\t\t\tvar delegateType = typeof(DelegateType);\r\n\r\n\t\t\t// Static method delegate\r\n\t\t\tif (method.IsStatic)\r\n\t\t\t{\r\n\t\t\t\treturn (DelegateType)Delegate.CreateDelegate(delegateType, method);\r\n\t\t\t}\r\n\r\n\t\t\tvar declaringType = method.DeclaringType;\r\n\t\t\tif (declaringType != null && declaringType.IsInterface && !virtualCall)\r\n\t\t\t{\r\n\t\t\t\tthrow new ArgumentException(\"Interface methods must be called virtually\");\r\n\t\t\t}\r\n\r\n\t\t\t// Open instance method delegate ...\r\n\t\t\tif (instance is null)\r\n\t\t\t{\r\n\t\t\t\tvar delegateParameters = delegateType.GetMethod(\"Invoke\").GetParameters();\r\n\t\t\t\tif (delegateParameters.Length == 0)\r\n\t\t\t\t{\r\n\t\t\t\t\t// Following should throw an ArgumentException with the proper message string.\r\n\t\t\t\t\t_ = Delegate.CreateDelegate(typeof(DelegateType), method);\r\n\t\t\t\t\t// But in case it doesn't...\r\n\t\t\t\t\tthrow new ArgumentException(\"Invalid delegate type\");\r\n\t\t\t\t}\r\n\t\t\t\tvar delegateInstanceType = delegateParameters[0].ParameterType;\r\n\t\t\t\t// Exceptional case: delegate struct instance type cannot be created from an interface method.\r\n\t\t\t\t// This case is handled in the \"non-virtual call\" case, using the struct method and the matching delegate instance type.\r\n\t\t\t\tif (declaringType != null && declaringType.IsInterface && delegateInstanceType.IsValueType)\r\n\t\t\t\t{\r","sourceCodeStart":1787,"sourceCodeEnd":1823,"githubUrl":"https://github.com/pardeike/Harmony/blob/e7872dc17008bc0ca2b3de1f53fd4120dbd7a17c/Harmony/Tools/AccessTools.cs#L1787-L1823","documentation":"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.","triggerScenarios":"AccessTools.MethodDelegate<T>(interfaceMethod, instance, nonVirtualCall: false) - i.e. virtualCall parameter false - where method.DeclaringType.IsInterface is true.","commonSituations":"Wrapping a method obtained from typeof(IMyInterface).GetMethod(...) while disabling virtual dispatch, often to call a base/default implementation directly.","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."],"exampleFix":"// before\nvar d = AccessTools.MethodDelegate<Func<int>>(typeof(IMover).GetMethod(\"Step\"), mover, false);\n// after\nvar d = AccessTools.MethodDelegate<Func<int>>(typeof(IMover).GetMethod(\"Step\"), mover); // virtualCall defaults to true","handlingStrategy":"validation","validationCode":"if (method.DeclaringType?.IsInterface == true && !virtualCall)\n    throw new InvalidOperationException(\"Interface methods require virtualCall: true\");","typeGuard":"bool IsNonVirtualInterfaceCall(MethodInfo m, bool virtualCall) => m.DeclaringType?.IsInterface == true && !virtualCall;","tryCatchPattern":"try { var d = AccessTools.MethodDelegate<T>(method, instance, nonVirtual); }\ncatch (ArgumentException) when (method.DeclaringType?.IsInterface == true) { d = AccessTools.MethodDelegate<T>(method, instance); }","preventionTips":["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."],"tags":["reflection","delegate","interface","harmony"],"backgroundTag":"invalid-argument-value","analyzedSha":"e7872dc17008bc0ca2b3de1f53fd4120dbd7a17c","analyzedAt":"2026-09-15T22:47:11.550Z","contentChangedAt":"2026-09-15T22:47:11.550Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}