Difference between revisions of "Modding:Modder Guide/APIs/Reflection"

From Stardew Valley Wiki
Jump to navigation Jump to search
(move content from Modding:Modder Guide/APIs (only author is Pathoschild))
 
(expand)
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 <tt>helper.Reflection</tt> in your entry method, or <tt>this.Helper.Reflection</tt> 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.
  
 +
==Basic reflection==
 +
===Overview===
 +
SMAPI provides three overloaded methods to access code: <tt>GetField</tt>, <tt>GetProperty</tt>, and <tt>GetMethod</tt>. Each one takes three arguments:
 +
{| class="wikitable"
 +
|-
 +
! argument
 +
! purpose
 +
|-
 +
| <tt>obj</tt> or <tt>type</tt>
 +
| The instance (or type if static) which has the private field/property/method you want to access.
 +
|-
 +
| <tt>name</tt>
 +
| The name of the private field/property/method.
 +
|-
 +
| <tt>required</tt>
 +
| Whether to throw a descriptive exception if the field/property/method isn't found. Default true. If set to false, it will return <tt>null</tt> 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===
 +
<tt>GetField</tt> and <tt>GetProperty</tt> are used the same way. Both return an object with two methods: <tt>GetValue</tt> returns the current field/property value, and <tt>SetValue</tt> overrides their value.
 +
 +
You can get the value directly:
 
<source lang="c#">
 
<source lang="c#">
// did you pet your pet today?
+
// 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();
 +
</source>
 +
 +
Or you can keep a reference to the reflection data, and change the value separately:
 +
<source lang="c#">
 +
// set value of static field
 +
IReflectedField<int> soundTimer = this.Helper.Reflection.GetField<int>(typeof(Junimo), "soundTimer");
 +
soundTimer.SetValue(100);
 +
</source>
  
// what is the spirit forecast today?
+
If you need to access the field/property repeatedly, keeping the reflection object will improve performance.
 +
 
 +
===Methods===
 +
<tt>GetMethod</tt> returns an object with one overloaded method. The <tt>Invoke</tt> comes in two forms:
 +
* <tt>Invoke()</tt> calls a method with no return value.
 +
* <tt>Invoke&lt;T&gt;()</tt> calls a method which returns a value, where <tt>T</tt> is the expected return type.
 +
 
 +
For example, this calls the private <tt>TV.getFortuneForecast</tt> method and stores the value it returns:
 +
<source lang="c#">
 
string forecast = this.Helper.Reflection
 
string forecast = this.Helper.Reflection
 
   .GetMethod(new TV(), "getFortuneForecast")
 
   .GetMethod(new TV(), "getFortuneForecast")
 
   .Invoke<string>();
 
   .Invoke<string>();
 +
</source>
  
// randomise the mines
+
If the method expects arguments, you can just add those to the <tt>Invoke</tt> method:
if(Game1.currentLocation is MineShaft)
+
<source lang="c#">
  this.Helper.Reflection.GetField<Random>(Game1.currentLocation, "mineRandom").SetValue(new Random());
+
Vector2 spawnTile = new Vector2(25, 25);
 +
this.helper.Reflection
 +
  .GetMethod(Game1.getFarm(), "doSpawnCrow")
 +
  .Invoke(spawnTile);
 
</source>
 
</source>
  
This works with static or instance fields/methods, caches the reflection to improve performance, and will throw useful errors automatically when reflection fails.
+
==Advanced reflection==
 
+
If you need to do more, you can access the underlying C# reflection types:
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#">
 
<source 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>
 
</source>
 +
 +
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.

Revision as of 05:28, 28 May 2018

Creating SMAPI mods SMAPI mascot.png


Modding:Index

The reflection API lets you access fields, properties, or methods you otherwise couldn't access. You can use it from helper.Reflection in your entry method, or this.Helper.Reflection elsewhere in your entry class.

Intro

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.

Basic reflection

Overview

SMAPI provides three overloaded methods to access code: GetField, GetProperty, and GetMethod. Each one takes three arguments:

argument purpose
obj or type The instance (or type if static) which has the private field/property/method you want to access.
name The name of the private field/property/method.
required Whether to throw a descriptive exception if the field/property/method isn't found. Default true. If set to false, it will return null 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

GetField and GetProperty are used the same way. Both return an object with two methods: GetValue returns the current field/property value, and SetValue overrides their value.

You can get the value directly:

// get value of instance field
bool wasPet = this.Helper.Reflection.GetField<bool>(pet, "wasPetToday").GetValue();

Or you can keep a reference to the reflection data, and change the value separately:

// set value of static field
IReflectedField<int> soundTimer = this.Helper.Reflection.GetField<int>(typeof(Junimo), "soundTimer");
soundTimer.SetValue(100);

If you need to access the field/property repeatedly, keeping the reflection object will improve performance.

Methods

GetMethod returns an object with one overloaded method. The Invoke comes in two forms:

  • Invoke() calls a method with no return value.
  • Invoke<T>() calls a method which returns a value, where T is the expected return type.

For example, this calls the private TV.getFortuneForecast method and stores the value it returns:

string forecast = this.Helper.Reflection
   .GetMethod(new TV(), "getFortuneForecast")
   .Invoke<string>();

If the method expects arguments, you can just add those to the Invoke method:

Vector2 spawnTile = new Vector2(25, 25);
this.helper.Reflection
   .GetMethod(Game1.getFarm(), "doSpawnCrow")
   .Invoke(spawnTile);

Advanced reflection

If you need to do more, you can access the underlying C# reflection types:

FieldInfo field = this.Helper.Reflection.GetField<string>().FieldInfo;
MethodInfo method = this.Helper.Reflection.GetMethod().MethodInfo;

Or even use C# reflection directly. Note that SMAPI adds caching and optimisations which you'll need to handle yourself if you do this.