Unity Helpers

Logo

Treasure chest of Unity developer tools. Professional inspector tooling, high-performance utilities, spatial queries, and 20+ editor tools.

ReflectionHelpers — Fast, Safe Reflection for Hot Paths

TL;DR — When To Use

Visual

Reflection Scan

ReflectionHelpers is a set of utilities for high‑performance reflection in Unity projects. It generates and caches delegates to access fields and properties, call methods and constructors, and quickly create common collections — with safe fallbacks when dynamic IL isn’t available.

Why it exists

What it solves

When to use it

When not to use it

Caching Strategy Overview

ReflectionHelpers now partitions cached delegates by capability strategy so that expression, dynamic-IL, and reflection fallbacks never overwrite each other. Key points:

Current Implementation Summary

API Group Representative methods Primary strategy (Mono/Editor) Fallbacks (IL2CPP/WebGL/AOT) Caching Notes
Field access (boxed) GetFieldGetter(FieldInfo), GetFieldSetter(FieldInfo) Emit DynamicMethod IL (BuildFieldGetter/SetterIL) to cast/unbox target and box return CreateCompiled* builds expression delegates; otherwise wraps FieldInfo.GetValue/SetValue FieldGetterCache, FieldSetterCache, static equivalents Supports static + instance fields; struct writes box when IL emit unavailable (IL2CPP/WebGL)
Field access (typed) GetFieldGetter<TInstance,TValue>, GetFieldSetter<TInstance,TValue> Emit typed DynamicMethod (setters use by-ref) to avoid boxing Falls back to GetValue/SetValue wrappers; setter fallback boxes then copies back None (callers must hold returned delegate) TInstance must match declaring type; fastest only where IL emit allowed
Property access (boxed) GetPropertyGetter(PropertyInfo), GetPropertySetter(PropertyInfo) Emit DynamicMethod (Call/Callvirt) and box value types Expression-compiled wrapper; else PropertyInfo.GetValue/SetValue PropertyGetterCache, PropertySetterCache, static equivalents Handles non-public accessors; fallback reintroduces boxing/allocations
Property access (typed) GetPropertyGetter<TInstance,TValue>, GetPropertySetter<TInstance,TValue> Emit typed DynamicMethod with cast/unbox guards Direct reflection wrappers casting to TValue None Avoids boxing only on IL paths; static typed getter limited to static properties
Method invokers (boxed) GetMethodInvoker, GetStaticMethodInvoker, InvokeMethod Emit DynamicMethod to unpack object[] args and box return Expression wrappers; otherwise call MethodInfo.Invoke directly MethodInvokers, StaticMethodInvokers Works with private members; fallback incurs reflection cost per call
Method invokers (typed static) GetStaticMethodInvoker<…>, GetStaticActionInvoker<…> Emit DynamicMethod per arity (0–4) for direct call Try MethodInfo.CreateDelegate; else expression compile TypedStaticInvoker0-4, TypedStaticAction0-4 Signature-checked upfront; limited to four parameters today
Method invokers (typed instance) GetInstanceMethodInvoker<TInstance,…>, GetInstanceActionInvoker<TInstance,…> Emit DynamicMethod using ldarga for structs and Callvirt for refs Falls back to Delegate.CreateDelegate / expression lambdas TypedInstanceInvoker0-4, TypedInstanceAction0-4 Requires TInstance assignable to declaring type; fallback boxes structs
Constructors & factories GetConstructor, CreateInstance, GetParameterlessConstructor<T>, GetParameterlessConstructor Delegate factory prefers expression lambdas, falls back to dynamic IL newobj and finally reflection (ConstructorInfo.Invoke / Activator.CreateInstance) Reflection invoke (no emit) Constructors, ParameterlessConstructors, TypedParameterlessConstructors Works across Editor/IL2CPP; capability overrides let tests force fallback paths
Indexer helpers GetIndexerGetter, GetIndexerSetter Expression lambdas or dynamic IL to handle struct receivers and value conversions Reflection PropertyInfo.Get/SetValue with argument validation IndexerGetters, IndexerSetters Throws IndexOutOfRangeException/InvalidCastException when indices mismatch; respects capability overrides
Collection creators CreateArray, GetListCreator(Type), GetDictionaryWithCapacityCreator Emit DynamicMethod for newarr/newobj, plus HashSet.Add wrappers Use Array.CreateInstance, Activator.CreateInstance, or reflection Invoke ArrayCreators, ListCreators, ListWithCapacityCreators, HashSetWithCapacityCreators, adders Create* APIs cache by element type; fallback still functional but allocates
Type/attribute scanning GetAllLoadedAssemblies, GetTypesDerivedFrom<T>, HasAttributeSafe Direct reflection with guarded iteration; Editor uses UnityEditor.TypeCache shortcuts Gracefully skips assemblies/types on error; no IL emit needed TypeResolutionCache, FieldLookup, PropertyLookup, MethodLookup Depends on link.xml or addressables to keep members under IL2CPP stripping

Current Consumers Snapshot

Platform Capability Matrix

Target Environment Unity Backend DynamicMethod IL Emit Expression.Compile ReflectionHelpers Behaviour Notes
Editor (Windows/macOS/Linux) Mono / JIT ✅ Enabled (EMIT_DYNAMIC_IL) ✅ Enabled (SUPPORT_EXPRESSION_COMPILE) Uses IL-generated delegates for getters/setters/invokers; expression compile is a fallback if IL creation fails at runtime Same behaviour for play mode in editor; fastest path used during authoring tools and tests.
Standalone Player (Mono scripting backend) Mono / JIT ✅ Enabled ✅ Enabled Matches editor experience; cached IL delegates provide best throughput Applies to legacy desktop Mono builds (Windows/Mac/Linux) where JIT is available.
Standalone / Mobile / Console (IL2CPP) IL2CPP / AOT ❌ Disabled at compile time (ENABLE_IL2CPP blocks EMIT_DYNAMIC_IL) ⚠️ Disabled (SUPPORT_EXPRESSION_COMPILE undefined; CheckExpressionCompilationSupport returns false) Falls back to pre-built delegate wrappers or direct Invoke/GetValue with caching; still avoids repeated reflection lookups Covers Windows/macOS/iOS/Android/Consoles when built with IL2CPP. Requires link.xml (or addressables) to preserve reflected members.
WebGL Player IL2CPP / AOT (wasm) ❌ Disabled (UNITY_WEBGL && !UNITY_EDITOR) ⚠️ Disabled Uses expression-free reflection paths identical to IL2CPP builds; object boxing unavoidable for struct setters/invokers WebGL disallows runtime codegen; helpers rely on cached reflection only.
Burst-compiled jobs Burst ❌ Not permitted ❌ Not permitted ReflectionHelpers should not be called from Burst jobs; wrap calls on main thread or use precomputed data Burst forbids managed reflection; guard usage with Unity.Burst.NoAlias patterns or pre-bake data.
Server builds / headless (Mono) Mono / JIT ✅ Enabled ✅ Enabled Same as desktop Mono path; suitable for dedicated servers running on JIT Confirm EMIT_DYNAMIC_IL stays enabled unless IL2CPP server build is selected.
Continuous Integration Any Depends on selected backend Depends on backend Benchmarks skip doc writes when Helpers.IsRunningInContinuousIntegration is true, but helpers themselves behave per backend Use automated tests to validate both IL2CPP fallback and Mono fast paths.

Key APIs at a glance

Usage examples

  1. Fast field get/set (boxed)
public sealed class Player { public int Score; }

FieldInfo score = typeof(Player).GetField("Score");
var getScore = ReflectionHelpers.GetFieldGetter(score);     // object -> object
var setScore = ReflectionHelpers.GetFieldSetter(score);     // (object, object) -> void

var p = new Player();
setScore(p, 42);
UnityEngine.Debug.Log((int)getScore(p)); // 42
  1. Struct note: use typed ref setter
public struct Stat { public int Value; }
FieldInfo valueField = typeof(Stat).GetField("Value");

// Prefer typed ref setter for structs
var setValue = ReflectionHelpers.GetFieldSetter<Stat, int>(valueField);
Stat s = default;
setValue(ref s, 100);
// s.Value == 100
  1. Typed property getter
var prop = typeof(Camera).GetProperty("orthographicSize");
var getSize = ReflectionHelpers.GetPropertyGetter<Camera, float>(prop);
float size = getSize(UnityEngine.Camera.main);
  1. Typed property setter (variant)
var prop = typeof(TestPropertyClass).GetProperty("InstanceProperty");
var set = ReflectionHelpers.GetPropertySetter<TestPropertyClass, int>(prop);
var obj = new TestPropertyClass();
set(obj, 10);
  1. Fast static method invoker (two params, typed)
MethodInfo concat = typeof(string).GetMethod(
    nameof(string.Concat), new[] { typeof(string), typeof(string) }
);
var concat2 = ReflectionHelpers.GetStaticMethodInvoker<string, string, string>(concat);
string joined = concat2("Hello ", "World");
  1. Low‑allocation constructors
// Parameterless constructor
var newList = ReflectionHelpers.GetParameterlessConstructor<List<int>>();
List<int> list = newList();

// Constructor via ConstructorInfo
ConstructorInfo ci = typeof(Dictionary<string, int>)
    .GetConstructor(new[] { typeof(int) });
var ctor = ReflectionHelpers.GetConstructor(ci);
var dict = (Dictionary<string, int>)ctor(new object[] { 128 });
  1. Collection creators and HashSet adder
var makeArray = ReflectionHelpers.GetArrayCreator(typeof(Vector3));
Array positions = makeArray(256); // Vector3[256]

IList names = ReflectionHelpers.CreateList(typeof(string), 64); // List<string>

object set = ReflectionHelpers.CreateHashSet(typeof(int), 0); // HashSet<int>
var add = ReflectionHelpers.GetHashSetAdder(typeof(int));
add(set, 1);
add(set, 1);
add(set, 2);
// set contains {1, 2}
  1. Typed collection creators
var makeArrayT = ReflectionHelpers.GetArrayCreator<int>();
int[] ints = makeArrayT(128);

var makeListT = ReflectionHelpers.GetListCreator<string>();
IList strings = makeListT();

var makeSetT = ReflectionHelpers.GetHashSetWithCapacityCreator<int>();
HashSet<int> intsSet = makeSetT(64);
var addT = ReflectionHelpers.GetHashSetAdder<int>();
addT(intsSet, 5);
  1. Safe attribute scanning
bool hasObsolete = ReflectionHelpers.HasAttributeSafe<ObsoleteAttribute>(typeof(MyComponent));
var values = ReflectionHelpers.GetAllAttributeValuesSafe(typeof(MyComponent));
// e.g., values["Obsolete"] -> ObsoleteAttribute instance

Performance tips

Benchmarking & Verification

Testing fallback behaviour

When you need to validate the pure-reflection paths (for example, to mimic IL2CPP/WebGL behaviour), override the runtime capability probes inside a using scope:

using (ReflectionHelpers.OverrideReflectionCapabilities(expressions: false, dynamicIl: false))
{
    // Force expression + IL emit to be unavailable
    Func<TestConstructorClass> ctor = ReflectionHelpers.GetParameterlessConstructor<TestConstructorClass>();
    TestConstructorClass instance = ctor(); // Uses reflection fallback

    PropertyInfo indexer = typeof(IndexerClass).GetProperty("Item");
    var getter = ReflectionHelpers.GetIndexerGetter(indexer);
    var setter = ReflectionHelpers.GetIndexerSetter(indexer);
    setter(new IndexerClass(), 42, new object[] { 0 }); // reflection-based path
}

The helper restores the original capability state when disposed, so nested overrides remain safe. Runtime regression tests now cover constructors and indexers in both accelerated and fallback modes.

IL2CPP/WebGL notes

⚠️ IL2CPP Code Stripping Considerations

Important for IL2CPP builds (WebGL, iOS, Android, Consoles):

While ReflectionHelpers itself is IL2CPP-safe, Unity’s managed code stripping may remove types or members you’re trying to access via reflection. This affects any reflection-based code, not just ReflectionHelpers.

Symptoms of stripping issues:

Solution: Use link.xml to preserve reflected types

Create a link.xml file in your Assets folder:

<linker>
  <!-- Preserve types you access via reflection -->
  <assembly fullname="Assembly-CSharp">
    <!-- Preserve entire type and all members -->
    <type fullname="MyNamespace.MyReflectedClass" preserve="all"/>

    <!-- Or preserve specific members -->
    <type fullname="MyNamespace.AnotherClass">
      <method signature="System.Void DoSomething()" />
      <field name="importantField" />
      <property name="ImportantProperty" />
    </type>

    <!-- Preserve all types in a namespace -->
    <namespace fullname="MyNamespace.ReflectedTypes" preserve="all"/>
  </assembly>
</linker>

Best practices:

Examples of code that needs link.xml:

// ❌ Requires link.xml: Type accessed by name
Type t = Type.GetType("MyNamespace.MyClass");

// ✅ Safer: Direct type reference
Type t = typeof(MyClass);

// ❌ Requires link.xml: Field accessed by name
FieldInfo field = typeof(MyClass).GetField("myField", BindingFlags.NonPublic);

// ✅ Safer: If field is definitely there, link.xml ensures it won't be stripped

When ReflectionHelpers doesn’t need link.xml:

Thread‑safety

Common pitfalls

See also