pardeike/Harmony · error · ArgumentException

Either FieldDeclaringType must be a class or field must be…

Error message

Either FieldDeclaringType must be a class or field must be static

What it means

FieldRefAccess<F>(fieldInfo) throws this ArgumentException when given a non-static field whose declaring type is a value type. Ref-returning delegates cannot address fields inside structs, and the generic class constraint on T is not enough because callers may pass a FieldInfo from a struct. The check enforces: either the field is static, or its declaring type is a class.

Solutions

  1. Only pass static FieldInfos or FieldInfos declared on classes to FieldRefAccess<F>
  2. For struct instance fields, fall back to FieldInfo.GetValue/SetValue with boxing
  3. For static fields on structs, this overload still works (T is ignored for static fields)

Example fix

// before
var fi = AccessTools.Field(typeof(Vector3), "x");
var acc = AccessTools.FieldRefAccess<float>(fi); // throws
// after
var fi = AccessTools.Field(typeof(Player), "hp");
var acc = AccessTools.FieldRefAccess<int>(fi);
Defensive patterns

Strategy: validation

Validate before calling

if (fieldInfo.IsStatic is false && fieldInfo.DeclaringType is Type dt && dt.IsValueType)
    throw new InvalidOperationException("Cannot create ref delegate for struct instance field");

Prevention

When it happens

Trigger: Calling AccessTools.FieldRefAccess<F>(fieldInfo) with a FieldInfo for an instance field declared on a struct (IsStatic == false, DeclaringType.IsValueType == true).

Common situations: Passing a struct's instance FieldInfo (e.g. a Vector3 field) into the delegate factory; generic helper code that collected FieldInfos from mixed class/struct types.

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/ba34a81186016561. Report an issue: GitHub.

Appendix: source

Thrown at Harmony/Tools/AccessTools.cs:1374

		///
		public static FieldRef<object, F> FieldRefAccess<F>(Type type, string fieldName)
		{
			if (type is null)
				throw new ArgumentNullException(nameof(type));
			if (fieldName is null)
				throw new ArgumentNullException(nameof(fieldName));
			try
			{
				var fieldInfo = Field(type, fieldName);
				if (fieldInfo is null)
					throw new MissingFieldException(type.Name, fieldName);
				// Backwards compatibility: This supports static fields, even those defined in structs. For static fields, T is effectively ignored.
				if (fieldInfo.IsStatic is false && 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("Either FieldDeclaringType must be a class or field must be static");
				}
				// Field's declaring type cannot be object, since object has no fields, so always need a castclass for T=object.
				return Tools.FieldRefAccess<object, F>(fieldInfo, needCastclass: true);
			}
			catch (Exception ex)
			{
				throw new ArgumentException($"FieldRefAccess<{typeof(F)}> for {type}, {fieldName} caused an exception", ex);
			}
		}

		/// <summary>Creates a field reference delegate for an instance field of a class or static field (NOT an instance field of a struct)</summary>
		/// <typeparam name="F"> type of the field</typeparam>
		/// <param name="typeColonName">The member in the form <c>TypeFullName:MemberName</c>, where TypeFullName matches the form recognized by <a href="https://docs.microsoft.com/en-us/dotnet/api/system.type.gettype">Type.GetType</a> like <c>Some.Namespace.Type</c>.</param>
		/// <returns>A readable/assignable <see cref="FieldRef{T,F}"/> delegate with <c>T=object</c></returns>
		///
		public static FieldRef<object, F> FieldRefAccess<F>(string typeColonName)
		{
			var info = Tools.TypColonName(typeColonName);

View on GitHub (pinned to e7872dc170)