{"record":{"id":"ff4366f545b9da16","repo":"pardeike/Harmony","slug":"fieldrefaccess-return-type-must-be-assignable-from-fieldtype","errorCode":null,"errorMessage":"FieldRefAccess return type must be assignable from FieldType for reference types","messagePattern":"FieldRefAccess return type must be assignable from FieldType for reference types","errorType":"exception","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"Harmony/Tools/Tools.cs","lineNumber":50,"sourceCode":"\t\t\tvar fieldType = fieldInfo.FieldType;\r\n\t\t\tif (returnType == fieldType)\r\n\t\t\t\treturn;\r\n\t\t\tif (fieldType.IsEnum)\r\n\t\t\t{\r\n\t\t\t\tvar underlyingType = Enum.GetUnderlyingType(fieldType);\r\n\t\t\t\tif (returnType != underlyingType)\r\n\t\t\t\t\tthrow new ArgumentException(\"FieldRefAccess return type must be the same as FieldType or \" +\r\n\t\t\t\t\t\t$\"FieldType's underlying integral type ({underlyingType}) for enum types\");\r\n\t\t\t}\r\n\t\t\telse if (fieldType.IsValueType)\r\n\t\t\t{\r\n\t\t\t\t// Boxing/unboxing is not allowed for ref values of value types.\r\n\t\t\t\tthrow new ArgumentException(\"FieldRefAccess return type must be the same as FieldType for value types\");\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tif (returnType.IsAssignableFrom(fieldType) is false)\r\n\t\t\t\t\tthrow new ArgumentException(\"FieldRefAccess return type must be assignable from FieldType for reference types\");\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tinternal static FieldRef<T, F> FieldRefAccess<T, F>(FieldInfo fieldInfo, bool needCastclass)\r\n\t\t{\r\n\t\t\tValidateFieldType<F>(fieldInfo);\r\n\t\t\tvar delegateInstanceType = typeof(T);\r\n\t\t\tvar declaringType = fieldInfo.DeclaringType;\r\n\r\n\t\t\tvar dm = new DynamicMethodDefinition($\"__refget_{delegateInstanceType.Name}_fi_{fieldInfo.Name}\",\r\n\t\t\t\ttypeof(F).MakeByRefType(), [delegateInstanceType]);\r\n\r\n\t\t\tvar il = dm.GetILGenerator();\r\n\t\t\t// Backwards compatibility: This supports static fields, even those defined in structs.\r\n\t\t\tif (fieldInfo.IsStatic)\r\n\t\t\t{\r\n\t\t\t\t// ldarg.0 + ldflda actually works for static fields, but the potential castclass (and InvalidCastException) below must be avoided\r\n\t\t\t\t// so might as well use the singular ldsflda for static fields.\r","sourceCodeStart":32,"sourceCodeEnd":68,"githubUrl":"https://github.com/pardeike/Harmony/blob/e7872dc17008bc0ca2b3de1f53fd4120dbd7a17c/Harmony/Tools/Tools.cs#L32-L68","documentation":"Tools.ValidateFieldType<F> for reference-type fields requires the FieldRefAccess delegate's return type F to be assignable FROM the field's type (returnType.IsAssignableFrom(fieldType)). Since the delegate returns a ref to the field, F must be the field type or a base/interface type of it; otherwise ArgumentException is thrown.","triggerScenarios":"Calling FieldRefAccess<T,F> where the field is a reference type and F is unrelated or is a DERIVED type of the field type — e.g. field declared as object but F=string, or field declared as Stream but F=FileStream.","commonSituations":"Developers inverting the assignability direction (they can ref-read a derived field as its base, not vice versa); generic helpers that pick the wrong F; target library narrowing or widening a field's declared type across versions.","solutions":["Set F to the exact declared field type, which always works: FieldRefAccess<T, FieldType>(field)","If a base/interface view is needed, choose F as a type where F.IsAssignableFrom(field.FieldType) is true (base class or interface of the field type)","Check statically before calling: typeof(F).IsAssignableFrom(fieldInfo.FieldType), and fall back to GetValue/SetValue if not"],"exampleFix":"// before\nvar rf = Tools.FieldRefAccess<C, FileStream>(typeof(C).GetField(\"stream\")); // field is Stream\n// after\nvar rf = Tools.FieldRefAccess<C, Stream>(typeof(C).GetField(\"stream\"));","handlingStrategy":"validation","validationCode":"if (!fieldInfo.FieldType.IsValueType && !typeof(F).IsAssignableFrom(fieldInfo.FieldType))\n    throw new InvalidOperationException($\"F={typeof(F)} is not assignable from field type {fieldInfo.FieldType}\");","typeGuard":"static bool ValidRefTypeRef<F>(FieldInfo f) =>\n    f.FieldType.IsValueType || typeof(F).IsAssignableFrom(f.FieldType);","tryCatchPattern":"try { rf = Tools.FieldRefAccess<T, F>(field); }\ncatch (ArgumentException) { rf = null; /* use fieldInfo.GetValue/SetValue instead */ }","preventionTips":["Remember the direction: F must be a base/interface OF the field type, not a derived type","Default to F = exact declared field type; only widen deliberately","Re-verify assignability after target-library updates that change field declared types"],"tags":["argument-exception","type-mismatch","assignability","field-ref"],"backgroundTag":"type-mismatch","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"}