Unity 6.3
0 онлайн 96 гостей 3 в системе
Вход
Оптимизация Шаг 10 из 208

Избегайте 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.");
    }
}

Дополнительные ресурсы