简体   繁体   English

如何在 c# 中使用反射 SetValue 进行优化?

[英]How to optimize using reflection SetValue in c#?

What I am trying to do: I am trying to make component based Objects which can be easly created with custom set type of rules for each component value.我正在尝试做的事情:我正在尝试制作基于组件的对象,这些对象可以使用每个组件值的自定义设置类型的规则轻松创建。

How I am doing it: I have created IComponent interface which each component implements.我是怎么做的:我创建了每个组件实现的IComponent接口。 All components need to be structs, example:所有组件都需要是结构体,例如:

public struct Weight : IComponent
{
     int weight;
}

Each Object is defined by just list of components with their values.每个 Object 仅由组件列表及其值定义。 Then to make it custom set of rules I made ObjectSettings which holds list of generic class ComponentSetup<T> where T: IComponent .然后为了使其自定义规则集,我制作了 ObjectSettings ,其中包含通用 class ComponentSetup<T> where T: IComponent的列表。 ComponentSetup is a class which by reflection gets list of fields in IComponent and pairs them in Dicionary as FieldName and GenerationType for field. ComponentSetup是一个 class ,它通过反射获取 IComponent 中的字段列表,并将它们在 Dicionary 中作为 FieldName 和 GenerationType 配对。 For example: for Object "Car":例如:对于 Object“汽车”:

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

For Object "Human":对于 Object “人类”:

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

For Object "1kg dumbbell":对于 Object“1kg 哑铃”:

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

In order to get generated Objects I used reflection to set values of components compose in List and return as Object.为了获得生成的对象,我使用反射来设置组成列表的组件的值,并以 Object 的形式返回。

The problem with this approach: When I want to generate 5k-10k of those Objects it takes way too much time.这种方法的问题是:当我想生成 5k-10k 这些对象时,它需要太多时间。

My solution so far: I generate semi filled objects(on startup) and store them as prefabs in PrefabManager.到目前为止我的解决方案:我生成半填充对象(在启动时)并将它们作为预制件存储在 PrefabManager 中。 They are Objects with components' values set only if their GenerationType is "Fixed" and then only fill values with other types of Generation.它们是仅当其 GenerationType 为“Fixed”时才设置组件值的对象,然后仅使用其他类型的 Generation 填充值。

My question: How can I make setting values by reflection faster, if it's not possible then how can I get the same result but faster?我的问题:如何通过反射更快地设置值,如果不可能,那么我怎样才能更快地获得相同的结果? I also would like to keep prefab generation on startup because they help me instantiating Objects because I don't need to create whole new object, just copy prefab and fill it, which is faster in my case.我还想在启动时继续生成预制件,因为它们可以帮助我实例化对象,因为我不需要创建全新的 object,只需复制预制件并填充它,这在我的情况下更快。

EDIT: Adding example code.编辑:添加示例代码。 I didn't test it however it should be easy to understand what I am trying to do:我没有对其进行测试,但是应该很容易理解我要做什么:

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);
    }
}

} }

Reflection is slow, but the execution of delegates is fast.反射很慢,但委托的执行速度很快。 Therefore, if you need to execute things obtained by reflection very often, it is good practise to use reflection to create a delegate and that use that delegate.因此,如果您需要经常执行通过反射获得的东西,最好使用反射创建一个委托并使用该委托。

Creating the delegate is very simple, as long as you know the type of the property and the type that declares the property.创建委托很简单,只要知道属性的类型和声明属性的类型即可。 The easiest way to get them is to have these types as generic type parameters in an open generic type.获取它们的最简单方法是将这些类型作为泛型类型参数放在开放的泛型类型中。 Then, you can close the type at runtime (using MakeGenericType on System.Type ) and instantiate the closed generic type using System.Activator.CreateInstance .然后,您可以在运行时关闭该类型(使用System.Type上的MakeGenericType )并使用System.Activator.CreateInstance实例化关闭的泛型类型。 This is of course costly, but you only need to create objects that describe the properties of your models once and then use it as factories for as many instances as you like without any reflection calls.这当然代价高昂,但您只需要创建一次描述模型属性的对象,然后将其用作您喜欢的任意数量实例的工厂,而无需任何反射调用。

Edit: this is how it can look like using properties instead of fields, based on your example code If you really want to go with fields (which I would encourage you not to do), creating the delegate is slightly more complex (use the expression compiler or emit IL code), but the principal approach remains the same.编辑:根据您的示例代码,这就是使用属性而不是字段的样子编译器或发出 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();
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM