Reflection - tModLoader/tModLoader GitHub Wiki

Introduction

Terraria is programmed in C#. In C#, access modifiers such as public, internal, and private are used to restrict how classes, methods, properties, and fields are accessed. For example, something with an accessibility of private will not be accessible outside of the class itself. Proper usage of access modifiers is a sign of well structured code, and usually there is little reason to bypass these access modifiers.

As modders, however, we sometimes need to access something not normally accessible due to the nature of modding a game. Since we can't implement the code directly in the Terraria source code, we need to bypass the access restrictions using something called Reflection.

When using Reflection, we must be aware of the implications of accessing inaccessible members. For example, something marked as private might be that way because the class needs to adjust other fields in tandem with that field to keep the code working correctly. Make sure to study the code containing the members you are using reflection to access to ensure that the behavior is maintained.

Do note that tModLoader itself will change the access modifiers of various members depending on how useful they would be to modders. It might seem like tModLoader could just change everything in Terraria to public, but we do not do that because access modifiers are useful and help modders write correct code. Please feel free to suggest changes if they would benefit modders.

Finally, be aware that by using reflection your mod could stop working when tModLoader updates. tModLoader developers try not to break mods by not changing public methods and fields, but private methods and fields have no such guarantee.

Basic Concepts

There are many well-written tutorials online about "c# reflection". Rather than reiterate the basic concepts and information about reflection, this guide directs the reader to consult those tutorials to establish a basic understanding of the concepts. Google "c# reflection" and follow whatever tutorials you find until you feel somewhat comfortable, then come back to this page to see how reflection can be used in tModLoader.

Note that this guide has not yet been updated to new UnsafeAccessorAttribute approach to reflection added in .NET 8, this guide will be updated at a later date with that info.

Accessing a private field

In NPC there is a static field named defaultSpawnRate that is private. We want to read the value. First, we use reflection to get a FieldInfo. FieldInfo is a special class that facilitates accessing the field.

FieldInfo NPCDefaultSpawnRateFieldInfo = typeof(NPC).GetField("defaultSpawnRate", BindingFlags.NonPublic | BindingFlags.Static);

Next, we use the FieldInfo to access the value. Since the field is static, we pass in null as the object argument.

Main.NewText("defaultSpawnRate is " + NPCDefaultSpawnRateFieldInfo.GetValue(null));

To set the value, we use the SetValue method. Once again we pass in null as the object argument. Make sure the value you are passing in matches the Type of the field, it is up to the programmer to do this when using reflection.

NPCDefaultSpawnRateFieldInfo.SetValue(null, 300);

Accessing a private field of a private class

Some classes in Terraria are private. In this case, we must use reflection to access the Type of the class rather than use the typeof operator. To use the GetType method, we need an Assembly instance. For Terraria classes we can use typeof(Mod).Assembly to retrieve that. Use the assembly to retrieve the Type, then use reflection as normal to access private fields of that class.

var UIGridType = typeof(Mod).Assembly.GetType("Terraria.ModLoader.UI.Elements.UIGrid");
FieldInfo _innerListFieldInfo = UIGridType.GetField("_innerList", BindingFlags.Instance | BindingFlags.NonPublic);

Using reflection with members of other mods

Reflection is used in 2 ways when dealing with other mods.

The first way is to access private members as we have done previously. This is useful for mods with strong or weak references to the target mod.

The second way to to access members of other mods without any reference at all. In this case, your code can't access the fields not because they are private, but because they may or may not exist at all. This can be useful for loose cross mod compatibility, such as a mod looking for a specifically named field in a class to drive some effect.

Reflection for other mods is the same as above, but we need to access the Assembly of the Mod containing the Type. After retrieving the Mod instance with ModLoader.GetMod or ModLoader.TryGetMod, the Code field of the Mod class is the Assembly object we need.

Mod ExampleMod = ModLoader.GetMod("ExampleMod");
FieldInfo ExampleMagicMirrorItemNameCycleColorsFieldInfo = ExampleMod.Code.GetType("ExampleMod.Content.Items.Tools.ExampleMagicMirror").GetField("itemNameCycleColors");

Color[] MyItemNameCycleColors = {
	Color.Red,
	Color.Orange,
	Color.Yellow,
};
ExampleMagicMirrorItemNameCycleColorsFieldInfo.SetValue(null, MyItemNameCycleColors);

Clean Code

When using reflection, sometimes the code can get a bit verbose. The code examples in this guide are written with the intention of accessing or setting a reflected member once for the sake of simplicity. It is useful to use other programming approaches if accessing a member multiple times.

Store MemberInfo instance

Rather than use GetField/GetMethod/GetProperty each time the code should access a private member, you can store the MemberInfo as a field in your class and reuse it. This should be done for efficiency and convenience. Reflection is slow, so retrieving the MemberInfo only once and reusing it will speed up code that needs to use reflection.

TODO: Example