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

From Stardew Valley Wiki
Jump to navigation Jump to search
(expand)
m (Text replacement - "tt>" to "samp>")
 
(3 intermediate revisions by 2 users not shown)
Line 1: Line 1:
 
{{../../header}}
 
{{../../header}}
  
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.
+
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.
  
 
==Intro==
 
==Intro==
Line 8: Line 8:
 
==Basic reflection==
 
==Basic reflection==
 
===Overview===
 
===Overview===
SMAPI provides three overloaded methods to access code: <tt>GetField</tt>, <tt>GetProperty</tt>, and <tt>GetMethod</tt>. Each one takes three arguments:
+
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"
 
{| class="wikitable"
 
|-
 
|-
Line 14: Line 14:
 
! purpose
 
! purpose
 
|-
 
|-
| <tt>obj</tt> or <tt>type</tt>
+
| <samp>obj</samp> or <samp>type</samp>
 
| The instance (or type if static) which has the private field/property/method you want to access.
 
| The instance (or type if static) which has the private field/property/method you want to access.
 
|-
 
|-
| <tt>name</tt>
+
| <samp>name</samp>
 
| The name of the private field/property/method.
 
| The name of the private field/property/method.
 
|-
 
|-
| <tt>required</tt>
+
| <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 <tt>null</tt> instead and you should validate it yourself.
+
| 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.
 
|}
 
|}
  
Line 27: Line 27:
  
 
===Fields and properties===
 
===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.
+
<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:
 
You can get the value directly:
<source lang="c#">
+
<syntaxhighlight lang="c#">
 
// get value of instance field
 
// 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>
+
</syntaxhighlight>
  
 
Or you can keep a reference to the reflection data, and change the value separately:
 
Or you can keep a reference to the reflection data, and change the value separately:
<source lang="c#">
+
<syntaxhighlight lang="c#">
 
// set value of static field
 
// set value of static field
 
IReflectedField<int> soundTimer = this.Helper.Reflection.GetField<int>(typeof(Junimo), "soundTimer");
 
IReflectedField<int> soundTimer = this.Helper.Reflection.GetField<int>(typeof(Junimo), "soundTimer");
 
soundTimer.SetValue(100);
 
soundTimer.SetValue(100);
</source>
+
</syntaxhighlight>
  
 
If you need to access the field/property repeatedly, keeping the reflection object will improve performance.
 
If you need to access the field/property repeatedly, keeping the reflection object will improve performance.
  
 
===Methods===
 
===Methods===
<tt>GetMethod</tt> returns an object with one overloaded method. The <tt>Invoke</tt> comes in two forms:
+
<samp>GetMethod</samp> returns an object with one overloaded method. The <samp>Invoke</samp> comes in two forms:
* <tt>Invoke()</tt> calls a method with no return value.
+
* <samp>Invoke()</samp> 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.
+
* <samp>Invoke&lt;T&gt;()</samp> calls a method which returns a value, where <samp>T</samp> is the expected return type.
  
For example, this calls the private <tt>TV.getFortuneForecast</tt> method and stores the value it returns:
+
For example, this calls the private <samp>TV.getFortuneForecast</samp> method and stores the value it returns:
<source lang="c#">
+
<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>();
</source>
+
</syntaxhighlight>
  
If the method expects arguments, you can just add those to the <tt>Invoke</tt> method:
+
If the method expects arguments, you can just add those to the <samp>Invoke</samp> method:
<source lang="c#">
+
<syntaxhighlight lang="c#">
 
Vector2 spawnTile = new Vector2(25, 25);
 
Vector2 spawnTile = new Vector2(25, 25);
 
this.helper.Reflection
 
this.helper.Reflection
 
   .GetMethod(Game1.getFarm(), "doSpawnCrow")
 
   .GetMethod(Game1.getFarm(), "doSpawnCrow")
 
   .Invoke(spawnTile);
 
   .Invoke(spawnTile);
</source>
+
</syntaxhighlight>
  
 
==Advanced reflection==
 
==Advanced reflection==
 
If you need to do more, you can access the underlying C# reflection types:
 
If you need to do more, you can access the underlying C# reflection types:
<source lang="c#">
+
<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.
 
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.

Latest revision as of 18:48, 4 November 2021

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.