Reflection

Reflection is a powerful way to detect an entity’s infomation. With reflection, you can retrieve a lot (such as methods or fields in a class) and filter them with BindingFlags. But if you just want to access a specific private field using a Reflection Helper is the better way. To see more, you can read through Microsoft’s tutorial.

Reflection Helper

The Modding API has it own reflection helper, but 56 made another reflection in Vasi which he named Mirror. Mirror will be easier to use and more powerful, but I’ll still introduce API’s reflection helper, you can skip this part if you like

Assume there are two class: one is a static class, other is a normal class

public static class StaticClass
{
        private static string _static_field = "a static field in a static class";
}
public class InstanceClass
{
        private static string _static_field = "a static field in an instance class";
        private int _non_static_field = 59;
}

I’ll show you how to access the private field for the above classes with api and vasi, you can try them for accessing classes in Assembly-CSharp

API Reflection Helper

GET

get StaticClass._static_field

FieldInfo fi = ReflectionHelper.GetField(typeof(StaticClass), "_static_field", false);
Log($"StaticClass._static_field is {fi.GetValue(null)}");

get InstanceClass._static_field

fi = ReflectionHelper.GetField(typeof(InstanceClass), "_static_field",false);
Log($"(First way)InstanceClass._static_field is {fi.GetValue(null)}");

Note

To Get or Set an instance field, you must have the instance of this type. In this example, I use InstanceClass instance = new InstanceClass(); to create an instance for test

get InstanceClass._non_static_field

// first way
fi = ReflectionHelper.GetField(typeof(InstanceClass), "_non_static_field");
Log($"(First way)InstanceClass._non_static_field is {fi.GetValue(instance)}");

// second way
int nonstatic = ReflectionHelper.GetAttr<InstanceClass, int>(instance,"_non_static_field");
Log($"(second way)InstanceClass._non_static_field is {nonstatic}");

SET

set StaticClass._static_field

FieldInfo fi = ReflectionHelper.GetField(typeof(StaticClass), "_static_field", false);
fi.SetValue(null, "modify static field in static");
Log($"StaticClass._static_field is {fi.GetValue(null)}"); // verify change

set InstanceClass._static_field

fi = ReflectionHelper.GetField(typeof(InstanceClass), "_static_field",false);
fi.SetValue(null, "modify static field in instance");
Log($"(First way)InstanceClass._static_field is {fi.GetValue(null)}"); // verify

set InstanceClass._non_static_field

// first way
fi = ReflectionHelper.GetField(typeof(InstanceClass), "_non_static_field");
fi.SetValue(instance, 1);
Log($"(First way)InstanceClass._non_static_field is {fi.GetValue(instance)}");

// second way
ReflectionHelper.SetAttr<InstanceClass, int>(instance, "_non_static_field", 2);
int nonstatic = ReflectionHelper.GetAttr<InstanceClass, int>(instance,"_non_static_field");
Log($"(second way)InstanceClass._non_static_field is {nonstatic}");

Vasi Reflection Helper

GET

get StaticClass._static_field

It seems to not be supported.

get InstanceClass._static_field

string f1 = Mirror.GetField<InstanceClass, string>("_static_field");
Log($"InstanceClass._static_field is {f1}");

get InstanceClass._non_static_field

int f2 = Mirror.GetField<InstanceClass, int>(instance,"_non_static_field");
Log($"InstanceClass._non_static_field is {f2}");

SET

set StaticClass._static_field

It seems to not be supported.

set InstanceClass._static_field

// first way : use SetField
Mirror.SetField<InstanceClass, string>("_static_field", "modify static in instance");
string f1 = Mirror.GetField<InstanceClass, string>("_static_field");
Log($"InstanceClass._static_field is {f1}");

// second way : use ref
ref string rf1 = ref Mirror.GetFieldRef<InstanceClass, string>("_static_field");
rf1 = "(way2) modify static in instance";
f1 = Mirror.GetField<InstanceClass, string>("_static_field");
Log($"InstanceClass._static_field is {f1}");

set InstanceClass._non_static_field

// first way : use SetField
Mirror.SetField<InstanceClass, int>(instance, "_non_static_field", 1);
int f2 = Mirror.GetField<InstanceClass, int>(instance, "_non_static_field");
Log($"InstanceClass._non_static_field is {f2}");

// second way : use ref
ref int rf2 = ref Mirror.GetFieldRef<InstanceClass, int>(instance, "_non_static_field");
rf2 = 2;
f2 = Mirror.GetField<InstanceClass, int>(instance, "_non_static_field");
Log($"InstanceClass._non_static_field is {f2}");

Some usage for Reflection

Use Reflection to call private methods

Assume there are two classes like the following:

public static class StaticClass
{
    private static int _static_method()
    {
        return 26;
    }
    private static string _static_method_with_arg(string name)
    {
        return $"Hello, {name}";
    }
}
public class InstanceClass
{
    private static int _static_method()
    {
        return 80;
    }
    private string _non_static_method_with_arg(string name)
    {
        return $"Hello, {name}";
    }
}

You may use typeof(T).GetMethod(methodName,BindingFlags) to get a method. Now suppose you want to call StaticClass._static_method() so you can use var method = typeof(StaticClass).GetMethod("_static_method", BindingFlags.NonPublic | BindingFlags.Static) to get this method, and then use m.Invoke(null, new object[] { }); to call it.

I will give four examples for different method calling:

BindingFlags private_static_flags = BindingFlags.NonPublic | BindingFlags.Static;
BindingFlags private_instance_flags = BindingFlags.NonPublic | BindingFlags.Instance;
MethodInfo m;
string strReturn;
int intReturn;
InstanceClass instance = new InstanceClass();

// Invoke a static method in a static class (without arguments)
m = typeof(StaticClass).GetMethod("_static_method", private_static_flags);
intReturn = (int) m.Invoke(null, new object[] { });
Log("StaticClass._static_method() return:" + intReturn);

// Invoke a static method in a static class (with arguments)
m = typeof(StaticClass).GetMethod("_static_method_with_arg", private_static_flags);
strReturn = (string) m.Invoke(null, new object[] {"sawyer" });
Log("StaticClass._static_method_with_arg(\"sawyer\") return:" + strReturn);

// Invoke a static method in a instance class (without arguments)
m = typeof(InstanceClass).GetMethod("_static_method", private_static_flags);
intReturn = (int) m.Invoke(null, new object[] { });
Log("InstanceClass._static_method() return:" + intReturn);

// Invoke a instance method in a instance class (with arguments)
m = typeof(InstanceClass).GetMethod("_non_static_method_with_arg", private_instance_flags);
strReturn = (string) m.Invoke(instance, new object[] { "hollowknight" });
Log("instance._non_static_method_with_arg(\"hollowknight\") return:" + strReturn);

Use Reflection to create a series of classes

Sometimes we want to create an instance of a class, the code new XXX() would come into our mind but if we need to create a lot of instances with different classes, reflection will be more convenient.

Assume there is an abstract shape defined like this:

public abstract class AbstractShape
{
    public Vector2 position;
    public abstract void Draw();
    public AbstractShape()
    {
        Modding.Logger.Log("An Concrete class has been instantiated");
        Draw();
    }
}

And we still have its subclasses in a class like these:

public class Shapes
{
    public class RectShape : AbstractShape
    {
        float width;
        float height;
        public override void Draw()
        {
            Modding.Logger.Log("RectShape Drawn");
        }
    }
    public class CicrleShape : AbstractShape
    {
        float r;

        public override void Draw()
        {
            Modding.Logger.Log("CircleShape Drawn");
        }
    }
}

Now we can create all the classes’s instance in the Shapes using the following code:

// this will get an array like this {typeof(RectShape),typeof(CicrleShape)}
Type[] subclasses = typeof(Shapes).GetNestedTypes(BindingFlags.Public | BindingFlags.Instance);

foreach(Type t in subclasses)
{
        object shape = Activator.CreateInstance(t); //CreateInstance(Type) like new Type()
}

Source Code

You can get ArticleExam in my github and also get a real mod (P5RandomMod) in 56’s github, it uses reflection to modify a private array.