Harmony field-or-property reflection tolerance¶
When reflecting against a game member whose declaration (field vs property) is unclear or might change between game versions, use Traverse.Create(target).Field("Name").GetValue<T>() with a property fallback. The decompile may read a field one version and a property the next; accepting either keeps the patch resilient.
Problem¶
F0065 (Mods/PowerTransmitterPlus/RESEARCH.md:621-623):
PowerTransmitter.UsePowercheckscableNetwork == WirelessOutputNetwork, but it's unclear whetherWirelessOutputNetworkis a public field or property. UseTraverse.Create(t).Field("WirelessOutputNetwork").GetValue<CableNetwork>()with a property fallback.
A decompile showing the member on the right-hand side of an assignment can look like either a field or an auto-property; C# compiles them similarly but reflection must pick the right one. A direct typeof(X).GetField("WirelessOutputNetwork") returns null for a property, and vice versa.
Solution / recipe¶
Try the field path first; fall back to the property path if null.
var field = typeof(PowerTransmitter).GetField("WirelessOutputNetwork",
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
CableNetwork value;
if (field != null)
{
value = (CableNetwork)field.GetValue(transmitter);
}
else
{
var prop = typeof(PowerTransmitter).GetProperty("WirelessOutputNetwork",
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
value = (CableNetwork)prop?.GetValue(transmitter);
}
Traverse.Create(obj).Field("Name").GetValue<T>() (Harmony utility) hides the two-step check. F0065 recommends it as the concise form.
The pattern is specifically for members whose declaration-kind is uncertain. When the game version is pinned and the member is observably a field, a plain AccessTools.Field is simpler.
Verification history¶
- 2026-04-20: page created from the Research migration; single source (F0065).
Open questions¶
None at creation.