Avoid C# reflection overhead
Reduce garbage collector overhead by avoiding use of C# reflection at runtime.
Read time 3 minutesLast updated 13 days ago
Both the Mono and IL2CPP scripting backends internally cache all C# objects and Unity doesn't garbage collect them. This means the garbage collector continuously scans the cached C# reflection objects during the lifetime of your application, which causes unnecessary and potentially significant garbage collector overhead.
System.ReflectionTo minimize the garbage collector overhead, avoid using methods that create a lot of C# reflection objects at runtime, such as and . Instead, scan assemblies in the Unity Editor for the required data and serialize it or use code generation to make the data available for use at runtime.
Assembly.GetTypesType.GetMethodsScan assemblies in the Editor
The following example shows how to scan assemblies in the Editor, serialize the data, and use it at runtime without reflection. Suppose you want to find all types that implement an interface called and use that information at runtime. You can write an Editor script that scans for all types implementing and saves their names to a or a JSON file as follows:
IMyFeatureIMyFeatureScriptableObjectusing UnityEngine;using UnityEditor;using System;using System.Linq;using System.Reflection;using System.Collections.Generic;[CreateAssetMenu(menuName = "Feature/FeatureTypeList")]public class FeatureTypeList : ScriptableObject{ public List<string> typeNames;}public static class FeatureTypeScanner{ [MenuItem("Tools/Scan IMyFeature Types")] public static void ScanTypes() { var featureType = typeof(IMyFeature); var types = AppDomain.CurrentDomain.GetAssemblies() .SelectMany(a => a.GetTypes()) .Where(t => featureType.IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract) .Select(t => t.FullName) .ToList(); // Create/overwrite the asset var asset = ScriptableObject.CreateInstance<FeatureTypeList>(); asset.typeNames = types; AssetDatabase.CreateAsset(asset, "Assets/FeatureTypeList.asset"); AssetDatabase.SaveAssets(); Debug.Log($"Found {types.Count} types implementing IMyFeature. Saved to FeatureTypeList.asset"); }}
Run Tools > Scan IMyFeature Types from the menu. This will create containing the list of type names.
Assets/FeatureTypeList.assetAt runtime, you can load this asset and use the type names for instantiation via , or just for display. No reflection is needed if you use codegen, as follows:
Activator.CreateInstancepublic class FeatureTypeLoader : MonoBehaviour{ public FeatureTypeList featureTypes; void Start() { var assemblyName = this.GetType().Assembly.FullName; foreach (var typeName in featureTypes.typeNames) { Debug.Log("Instantiating feature type: " + typeName); Activator.CreateInstance(assemblyName, typeName); // You can also use Type.GetType(typeName) (both of these still uses reflection but only on the pre-selected feature types instead of casting a wider net across all sort of types in the assembly.) } }}
Use code generation
Instead of storing type names, you can generate a C# file at Editor time that contains a static array of types or delegates, eliminating all runtime reflection. The following code example generates a file with a static array of the discovered types, which you can use at runtime with no reflection:
Assets/GeneratedFeatureTypes.csusing UnityEditor;using System;using System.Linq;using System.Text;using System.IO;public static class FeatureTypeCodeGen{ [MenuItem("Tools/CodeGen IMyFeature Types")] public static void CodeGenTypes() { var featureType = typeof(IMyFeature); var types = AppDomain.CurrentDomain.GetAssemblies() .SelectMany(a => a.GetTypes()) .Where(t => featureType.IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract) .ToList(); StringBuilder sb = new StringBuilder(); sb.AppendLine("public static class GeneratedFeatureTypes {"); sb.AppendLine(" public static readonly System.Type[] Types = new System.Type[] {"); foreach (var t in types) { sb.AppendLine($" typeof({t.FullName}),"); } sb.AppendLine(" };"); sb.AppendLine("}"); File.WriteAllText("Assets/GeneratedFeatureTypes.cs", sb.ToString()); AssetDatabase.Refresh(); Debug.Log("Generated FeatureTypes.cs with " + types.Count + " types."); }}