Repository navigation
Code Structure
For my UMM mods, I've written the PlanetbaseModUtilities shared project that contains many utilities and helpers that are commonly used across the actual mods.
I decided to do this as a shared project that the other projects reference so that each mod only comprises a single .dll (and the Info.json file). This naturally makes it somewhat less portable to other outside projects. However, it would be straightforward to convert it into its own .dll to be distrubuted alongside the mods.
Within PlanetbaseModUtilities, the helpers are primarily extension methods for ease of development so that Planetbase objects can be acted upon directly without having to reference the helper itself. They all use the generic utility methods within CoreUtils so as to abstract all the direct reflection calls. For example:
The original getCategory() method is private so if we have an instance of Module we can't easily get its Category
public class Module : Construction
{
private Module.Category getCategory()
{
return ...
}
}Helper, styled as an extension method, which uses the generic InvokeMethod helper in CoreUtils. This keeps the extension method clean. The list of extension methods is expanded as and when writing a mod requires access to an inaccessible member, but they all follow the same simple format and are almost all single lines:
public static class BuildableUtils
{
public static Module.Category GetCategory(this Module module)
{
return CoreUtils.InvokeMethod<Module, Module.Category>("getCategory", module);
}
}'InvokeMethod' helper which handles the binding flags, casts and reflection. Overrides of InvokeMethod handle different combinations of flags etc, depending on the Types passed. There are GetMember and SetMember methods with respective overrides also for accessing fields and properties (And a GetEnumValue which is a real doozy)
public static class CoreUtils
{
public static V InvokeMethod<U, V>(string methodName, U containingInstance, params object[] parameters)
{
BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Instance;
return (V)typeof(U).GetMethod(methodName, flags).Invoke(containingInstance, parameters);
}
}We can now very easily use the original method, almost as if it were public in the first place.
var module = new Module();
// With the helper utils
var categoryWith = module.GetCategory();
// Without the helper utils
var categoryWithout = (Module.Category)module.GetType().GetMethod("getCategory", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(module);This structure required a bit more work up front to create the generics and extensions, but I find it much easier and cleaner to keep all of the reflection etc. abstracted out of the working mod code. It also means you don't have to write the above GetType().GetField(...) over and over every time you want to retrieve a single member.
Of course, it is still possible to do the reflection directly (or use the CoreUtils methods), but I try to avoid doing this for thoroughness.