Избегайте C# отражения над головой
Оба Mono и IL2CPP Скриптовые бэкэнды внутренне кэшируют все C# System.Reflection объекты, а Unity не собирает мусор с них. Это означает, что сборщик мусора непрерывно сканирует кэшируемые объекты отражения C# в течение срока службы приложения, что вызывает ненужные и потенциально значительные нагрузки на сборщик мусора.
Чтобы свести к минимуму накладные расходы сборщика мусора, избегайте использования методов, которые создают много объектов отражения C# во время выполнения, таких как Assembly.GetTypes и Type.GetMethods. Вместо этого сканируйте сборки в Unity Editor на наличие требуемых данных и сериализируйте их или используйте генерацию кода, чтобы сделать данные доступными для использования во время выполнения.
Сканирование сборок в редакторе
Следующий пример показывает, как просканировать сборки в редакторе, сериализовать данные и использовать их во время выполнения без рефлексии. Допустим, вы хотите найти все типы, реализующие интерфейс с именем IMyFeature Вы можете написать скрипт редактора, который сканирует все типы, реализуемые в IMyFeature и сохраняет их имена в ScriptableObject или файл JSON следующим образом:
using 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");
}
}
Запустите Tools > Scan IMyFeature Types из меню. Это создаст Assets/FeatureTypeList.asset, содержащий список имен типов.
Во время выполнения вы можете загрузить этот ассет и использовать имена типов для экземплярирования через Activator.CreateInstanceили просто для отображения. Отражение не требуется, если вы используете codegen, как показано ниже:
public 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.)
}
}
}
Используйте генерацию кода
Вместо хранения имён типов можно во время работы редактора сформировать файл C# со статическим массивом типов или делегатов, полностью исключив рефлексию во время выполнения. В следующем примере кода формируется файл Assets/GeneratedFeatureTypes.cs статическим массивом найденных типов, который можно использовать во время выполнения без рефлексии:
using 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.");
}
}