Как оптимизировать использование отражения SetValue в c#? - PullRequest
0 голосов
/ 13 июля 2020

Что я пытаюсь сделать: я пытаюсь создать объекты на основе компонентов, которые можно легко создать с помощью настраиваемого типа правил для каждого значения компонента.

Как я это делаю: я создал IComponent интерфейс, который реализует каждый компонент. Все компоненты должны быть структурами, например:

public struct Weight : IComponent
{
     int weight;
}

Каждый объект определяется просто списком компонентов с их значениями. Затем, чтобы сделать его собственным набором правил, я создал ObjectSettings, который содержит список общих c class ComponentSetup<T> where T : IComponent. ComponentSetup - это класс, который путем отражения получает список полей в IComponent и объединяет их в Dicionary как FieldName и GenerationType для поля. Например: для объекта «Автомобиль»:

Car:
    Weight:
         weight: 
            GenerationType: RandomRange
               Min: 1200
               Max: 1700

Для объекта «Человек»:

Human:
    Weight:
         weight: 
            GenerationType: NormalDistribution
               Expected Value: 70
               Variance: 4

Для объекта «Гантель 1 кг»:

1kgDumbbell:
    Weight:
         weight: 
            GenerationType: Fixed
               Value: 1

В Чтобы получить сгенерированные объекты, я использовал отражение, чтобы установить значения компонентов, составляющих в списке и возвращаемых как объект.

Проблема с этим подходом: когда я хочу сгенерировать 5-10 тыс. этих объектов, это занимает слишком много времени .

Мое решение: я генерирую полузаполненные объекты (при запуске) и сохраняю их как префабы в PrefabManager. Это объекты, значения компонентов которых установлены только в том случае, если их GenerationType имеет значение «Fixed», а затем заполняются только значения с другими типами генерации.

Мой вопрос: как я могу быстрее установить значения путем отражения, если это невозможно тогда как я могу получить тот же результат, но быстрее? Я также хотел бы сохранить генерацию префаба при запуске, потому что они помогают мне создавать экземпляры объектов, потому что мне не нужно создавать полностью новый объект, просто скопируйте префаб и заполните его, что в моем случае быстрее.

EDIT: Добавляем пример кода. Я не тестировал это, но должно быть легко понять, что я пытаюсь сделать:

namespace Example
{
//ProceduralObject Component intreface
public interface IComponent
{
}

//Example component for procedural object
public struct Weight : IComponent
{
    public int weight;
}

//object with procedurally generated components
public class ProceduralObject
{
    public List<IComponent> components = new List<IComponent>();
}


public class ProceduralObjectSettings
{
    public Dictionary<string,ComponentSetup> ComponentSetups = new Dictionary<string,ComponentSetup>();

    public ProceduralObjectSettings()
    {
    }

    public void AddComponent(Type t)
    {
        //check if added component is assignable from correct interface
        if (t.IsAssignableFrom(typeof(IComponent))) ComponentSetups.Add(t.Name,new ComponentSetup(t));
    }
    
    //getting ProceduralObject with generated components
    public ProceduralObject getGeneratedObject()
    {
        ProceduralObject newObject = new ProceduralObject();
        
        
        foreach (var componentSetup in ComponentSetups)
        {
            newObject.components.Add(componentSetup.Value.getGeneratedComponent());
        }

        return newObject;
    }
}

public class ComponentSetup 
{
    // Collection of properties of IComponent it represents
    public Dictionary<string, IGenerationType> propertyGenerationSettings = new Dictionary<string, IGenerationType>();
    // Type of IComponent it represents
    public Type t;
    public ComponentSetup(Type t)
    {
        this.t = t;
        
        //Getting all fields of represented IComponent and adding them to propertyGenerationSettings with default GenerationType
        var fields = t.GetFields();
        for (int i = 0; i < fields.Length; i++)
        {
            propertyGenerationSettings.Add(fields[i].Name,new EmptyGenerationType());
        }
    }
    
    //Generating new component with settings
    public IComponent getGeneratedComponent()
    {
        IComponent toReturn = (IComponent)Activator.CreateInstance(t);

        var fields = toReturn.GetType().GetFields();
        
        foreach (var property in propertyGenerationSettings)
        { 
            var fieldInfo = fields.First(field => field.Name == property.Key);
            toReturn.GetType().SetMemberValue(fieldInfo, property.Value.GetGeneratedValue());
        }

        return toReturn;
    }
}

public interface IGenerationType
{
    System.Object GetGeneratedValue();
}

public class EmptyGenerationType : IGenerationType
{
    public object GetGeneratedValue()
    {
        throw new Exception("You can't use EmptyGenerationType");
    }
}

public class RandomRangeGenerationType : IGenerationType
{
    private double min, max;
    public RandomRangeGenerationType(double min, double max)
    {
        this.min = min;
        this.max = max;
    }
    
    public object GetGeneratedValue()
    {
        return null; /* return */
    }
}

public class NormalDistributionGenerationType : IGenerationType
{
    private double expectedValue, variance;
    public  NormalDistributionGenerationType(double expectedValue, double variance)
    {
        this.expectedValue = expectedValue;
        this.variance = variance;
    }
    
    public object GetGeneratedValue()
    {
        return null; /* return */
    }
}

public class FixedGenerationType : IGenerationType
{
    public double value;

    public FixedGenerationType(double value)
    {
        this.value = value;
    }
    
    public object GetGeneratedValue()
    {
        return null;
    }
}


public class Example
{
    public void Main()
    {
        Dictionary<string,ProceduralObjectSettings> proceduralObjectsCollection = new Dictionary<string,ProceduralObjectSettings>();
        
        proceduralObjectsCollection.Add("Car",new ProceduralObjectSettings());
        proceduralObjectsCollection["Car"].AddComponent(typeof(Weight));
        proceduralObjectsCollection["Car"].ComponentSetups["Weight"].propertyGenerationSettings["weight"] = new RandomRangeGenerationType(1200,1700);
        
        proceduralObjectsCollection.Add("Human",new ProceduralObjectSettings());
        proceduralObjectsCollection["Human"].AddComponent(typeof(Weight));
        proceduralObjectsCollection["Human"].ComponentSetups["Weight"].propertyGenerationSettings["weight"] = new NormalDistributionGenerationType(70,4);
        
        proceduralObjectsCollection.Add("1kgDumbbell",new ProceduralObjectSettings());
        proceduralObjectsCollection["1kgDumbbell"].AddComponent(typeof(Weight));
        proceduralObjectsCollection["1kgDumbbell"].ComponentSetups["Weight"].propertyGenerationSettings["weight"] = new FixedGenerationType(1);
    }
}

}

1 Ответ

4 голосов
/ 13 июля 2020

Отражение происходит медленно, но выполнение делегатов выполняется быстро. Поэтому, если вам нужно очень часто выполнять вещи, полученные с помощью отражения, рекомендуется использовать отражение для создания делегата и использовать этот делегат.

Создание делегата очень просто, если вы знаете тип свойства и тип, который объявляет свойство. Самый простой способ получить их - иметь эти типы как общие параметры типа c в открытом общем типе c. Затем вы можете закрыть тип во время выполнения (используя MakeGenericType на System.Type) и создать экземпляр закрытого типа generi c, используя System.Activator.CreateInstance. Это, конечно, дорого, но вам нужно только один раз создать объекты, описывающие свойства ваших моделей, а затем использовать их в качестве фабрик для любого количества экземпляров без каких-либо вызовов отражения.

Изменить: вот как это может выглядеть с использованием свойств вместо полей, на основе вашего примера кода Если вы действительно хотите go с полями (чего я бы посоветовал вам не делать), создание делегата немного сложнее ( используйте компилятор выражений или испустите код IL), но основной подход остается тем же.

public class ComponentSetup
{
    // Collection of properties of IComponent it represents
    private Dictionary<string, PropertySetter> propertyGenerationSettings = new Dictionary<string, PropertySetter>();
    // Type of IComponent it represents
    public Type t;
    public ComponentSetup( Type t )
    {
        this.t = t;

        //Getting all fields of represented IComponent and adding them to propertyGenerationSettings with default GenerationType
        var fields = t.GetProperties();
        for(int i = 0; i < fields.Length; i++)
        {
            var propertySetterType = typeof( PropertySetter<,> ).MakeGenericType( t, fields[i].PropertyType );
            var setter = (PropertySetter)Activator.CreateInstance( propertySetterType, fields[i] );
            propertyGenerationSettings.Add( fields[i].Name, setter );
        }
    }

    public void SetGenerator<T>( string property, IGenerationType<T> generator )
    {
        propertyGenerationSettings[property].SetGenerator( generator );
    }

    //Generating new component with settings
    public IComponent getGeneratedComponent()
    {
        IComponent toReturn = (IComponent)Activator.CreateInstance( t );

        foreach(var property in propertyGenerationSettings)
        {
            property.Value.Set( toReturn );
        }

        return toReturn;
    }
}

internal abstract class PropertySetter
{
    public abstract void Set( object target );

    public abstract void SetGenerator( object generator );
}

internal class PropertySetter<T, TField> : PropertySetter
{
    private Action<T, TField> setter;
    private IGenerationType<TField> generator;

    public PropertySetter( PropertyInfo property )
    {
        setter = (Action<T, TField>)property.SetMethod.CreateDelegate( typeof( Action<T, TField> ) );
        generator = new EmptyGenerationType<TField>();
    }

    public override void Set( object target )
    {
        if(target is T targetObj)
        {
            setter( targetObj, generator.GetGeneratedValue() );
        }
    }

    public override void SetGenerator( object generator )
    {
        this.generator = (generator as IGenerationType<TField>) ?? this.generator;
    }
}

public interface IGenerationType<T>
{
    T GetGeneratedValue();
}
...