Line 1: |
Line 1: |
| {{../../header}} | | {{../../header}} |
| | | |
− | ===Reflection===
| + | The reflection API lets you access fields, properties, or methods you otherwise couldn't access. You can use it from <samp>helper.Reflection</samp> in your entry method, or <samp>this.Helper.Reflection</samp> elsewhere in your entry class. |
− | SMAPI provides an API for robustly accessing fields, properties, or methods you otherwise couldn't access, such as private fields. You can use it from <tt>helper.Reflection</tt> in your entry method, or <tt>this.Helper.Reflection</tt> elsewhere in your entry class.
| |
| | | |
− | Here are a few examples of what this lets you do:
| + | ==Intro== |
| + | [https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/reflection ''Reflection''] is a powerful C# feature which lets code analyse and interact with code. SMAPI provides a simplified reflection API focused on accessing private game fields, properties, and methods. SMAPI will automatically handle validation, caching, and performance optimisation. |
| | | |
− | <source lang="c#"> | + | ==Basic reflection== |
− | // did you pet your pet today? | + | ===Overview=== |
| + | SMAPI provides three overloaded methods to access code: <samp>GetField</samp>, <samp>GetProperty</samp>, and <samp>GetMethod</samp>. Each one takes three arguments: |
| + | {| class="wikitable" |
| + | |- |
| + | ! argument |
| + | ! purpose |
| + | |- |
| + | | <samp>obj</samp> or <samp>type</samp> |
| + | | The instance (or type if static) which has the private field/property/method you want to access. |
| + | |- |
| + | | <samp>name</samp> |
| + | | The name of the private field/property/method. |
| + | |- |
| + | | <samp>required</samp> |
| + | | Whether to throw a descriptive exception if the field/property/method isn't found. Default true. If set to false, it will return <samp>null</samp> instead and you should validate it yourself. |
| + | |} |
| + | |
| + | Each method returns an object you can use to interact further with it — like getting or setting the field/property value, or invoking the method. |
| + | |
| + | ===Fields and properties=== |
| + | <samp>GetField</samp> and <samp>GetProperty</samp> are used the same way. Both return an object with two methods: <samp>GetValue</samp> returns the current field/property value, and <samp>SetValue</samp> overrides their value. |
| + | |
| + | You can get the value directly: |
| + | <syntaxhighlight lang="c#"> |
| + | // get value of instance field |
| bool wasPet = this.Helper.Reflection.GetField<bool>(pet, "wasPetToday").GetValue(); | | bool wasPet = this.Helper.Reflection.GetField<bool>(pet, "wasPetToday").GetValue(); |
| + | </syntaxhighlight> |
| | | |
− | // what is the spirit forecast today? | + | Or you can keep a reference to the reflection data, and change the value separately: |
| + | <syntaxhighlight lang="c#"> |
| + | // set value of static field |
| + | IReflectedField<int> soundTimer = this.Helper.Reflection.GetField<int>(typeof(Junimo), "soundTimer"); |
| + | soundTimer.SetValue(100); |
| + | </syntaxhighlight> |
| + | |
| + | If you need to access the field/property repeatedly, keeping the reflection object will improve performance. |
| + | |
| + | ===Methods=== |
| + | <samp>GetMethod</samp> returns an object with one overloaded method. The <samp>Invoke</samp> comes in two forms: |
| + | * <samp>Invoke()</samp> calls a method with no return value. |
| + | * <samp>Invoke<T>()</samp> calls a method which returns a value, where <samp>T</samp> is the expected return type. |
| + | |
| + | For example, this calls the private <samp>TV.getFortuneForecast</samp> method and stores the value it returns: |
| + | <syntaxhighlight lang="c#"> |
| string forecast = this.Helper.Reflection | | string forecast = this.Helper.Reflection |
| .GetMethod(new TV(), "getFortuneForecast") | | .GetMethod(new TV(), "getFortuneForecast") |
| .Invoke<string>(); | | .Invoke<string>(); |
| + | </syntaxhighlight> |
| | | |
− | // randomise the mines | + | If the method expects arguments, you can just add those to the <samp>Invoke</samp> method: |
− | if(Game1.currentLocation is MineShaft)
| + | <syntaxhighlight lang="c#"> |
− | this.Helper.Reflection.GetField<Random>(Game1.currentLocation, "mineRandom").SetValue(new Random());
| + | Vector2 spawnTile = new Vector2(25, 25); |
− | </source> | + | this.helper.Reflection |
− | | + | .GetMethod(Game1.getFarm(), "doSpawnCrow") |
− | This works with static or instance fields/methods, caches the reflection to improve performance, and will throw useful errors automatically when reflection fails.
| + | .Invoke(spawnTile); |
− | | + | </syntaxhighlight> |
− | If you need to do more, you can switch to [https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/reflection C#'s underlying reflection API]:
| |
| | | |
− | <source lang="c#"> | + | ==Advanced reflection== |
| + | If you need to do more, you can access the underlying C# reflection types: |
| + | <syntaxhighlight lang="c#"> |
| FieldInfo field = this.Helper.Reflection.GetField<string>(…).FieldInfo; | | FieldInfo field = this.Helper.Reflection.GetField<string>(…).FieldInfo; |
| MethodInfo method = this.Helper.Reflection.GetMethod(…).MethodInfo; | | MethodInfo method = this.Helper.Reflection.GetMethod(…).MethodInfo; |
− | </source> | + | </syntaxhighlight> |
| + | |
| + | Or even use [https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/reflection C# reflection] directly. Note that SMAPI adds caching and optimisations which you'll need to handle yourself if you do this. |