pardeike/Harmony · error · ArgumentException
FieldDeclaringType must be a class
Error message
FieldDeclaringType must be a class
What it means
AccessTools.FieldRefAccess<T,F>(fieldInfo, instance) requires the field's DeclaringType to be a class because the emitted ref delegate uses ldflda on a typed reference. Instance fields on structs are unsupported here (use StructFieldRefAccess).
Solutions
- Switch to AccessTools.StructFieldRefAccess<T,F> when the declaring type is a struct
- Verify fieldInfo.DeclaringType is the class you expect, not a struct
- If fieldInfo was found via a derived/interface type, re-find it on the concrete class
- Pre-check declaringType.IsValueType before selecting the accessor
Example fix
// before var rf = AccessTools.FieldRefAccess<MyClass, int>(structFieldInfo); // after var rf = AccessTools.StructFieldRefAccess<MyStruct, int>(structFieldInfo);
Defensive patterns
Strategy: validation
Validate before calling
if (fieldInfo.DeclaringType is { IsValueType: true }) return AccessTools.StructFieldRefAccess<T, F>(fieldInfo); Type guard
static bool IsClassDeclaredInstanceField(FieldInfo fi) => fi is { IsStatic: false } && fi.DeclaringType is { IsValueType: false }; Prevention
- Never re-find fields via typeof(object) or interfaces when the concrete type is a struct
- Assert DeclaringType is a class before class-based FieldRefAccess
- Test accessors against the exact patched types
When it happens
Trigger: fieldInfo is a non-static instance field whose DeclaringType.IsValueType is true, passed to the class-instance FieldRefAccess overload.
Common situations: Fields discovered via typeof(object) or interface lookup that actually resolve to struct implementations; generic reflection helpers that process both classes and structs uniformly.
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
- T (FieldRefAccess instance type) must not be a value type
- Either FieldDeclaringType must be a class or field must be…
- FieldRefAccess< , > for caused an exception
- FieldRefAccess< , > for , caused an exception
- FieldRefAccess< > for , caused an exception
AI-assisted analysis of pardeike/Harmony@e7872dc170 (2026-09-15).
Data as JSON: /api/errors/30522477f10306e8.
Report an issue: GitHub.
Appendix: source
Thrown at Harmony/Tools/AccessTools.cs:1496
{
if (instance is null)
throw new ArgumentNullException(nameof(instance));
if (fieldInfo is null)
throw new ArgumentNullException(nameof(fieldInfo));
try
{
var delegateInstanceType = typeof(T);
if (delegateInstanceType.IsValueType)
throw new ArgumentException("T (FieldRefAccess instance type) must not be a value type");
if (fieldInfo.IsStatic)
throw new ArgumentException("Field must not be static");
var needCastclass = false;
if (fieldInfo.DeclaringType is Type declaringType)
{
// When fieldInfo is passed to FieldRefAccess methods, the T generic class constraint is insufficient to ensure that
// the field is not a struct instance field, since T could be object, ValueType, or an interface that the struct implements.
if (declaringType.IsValueType)
throw new ArgumentException("FieldDeclaringType must be a class");
needCastclass = Tools.FieldRefNeedsClasscast(delegateInstanceType, declaringType);
}
return ref Tools.FieldRefAccess<T, F>(fieldInfo, needCastclass)(instance);
}
catch (Exception ex)
{
throw new ArgumentException($"FieldRefAccess<{typeof(T)}, {typeof(F)}> for {instance}, {fieldInfo} caused an exception", ex);
}
}
/// <summary>A readable/assignable reference delegate to an instance field of a struct</summary>
/// <typeparam name="T">The struct that defines the instance field</typeparam>
/// <typeparam name="F">
/// The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type),
/// a type that <see cref="Type.IsAssignableFrom(Type)">is assignable from</see> that type; or if the field's type is an enum type,
/// either that type or the underlying integral type of that enum type
/// </typeparam>
/// <param name="instance">A reference to the runtime instance to access the field</param>
View on GitHub (pinned to e7872dc170)