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

  1. Switch to AccessTools.StructFieldRefAccess<T,F> when the declaring type is a struct
  2. Verify fieldInfo.DeclaringType is the class you expect, not a struct
  3. If fieldInfo was found via a derived/interface type, re-find it on the concrete class
  4. 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

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


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)