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

How to optimize using reflection SetValue in c#?

我正在尝试做的事情:我正在尝试制作基于组件的对象,可以使用每个组件值的自定义规则集类型轻松创建这些对象。

我是怎么做的:我创建了每个组件实现的 IComponent 接口。所有组件都需要是结构体,例如:

public struct Weight : IComponent
{
     int weight;
}

每个对象仅由组件列表及其值定义。然后,为了使其成为自定义规则集,我制作了 ObjectSettings,其中包含通用 class ComponentSetup<T> where T : IComponent 列表。 ComponentSetup 是一个 class,它通过反射获取 IComponent 中的字段列表,并在 Dicionary 中将它们配对为字段的 FieldName 和 GenerationType。例如:对于对象“汽车”:

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

对象“人类”:

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

对于对象“1kg 哑铃”:

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

为了获得生成的对象,我使用反射将组件的值设置为 List 和 return 作为对象。

这种方法的问题:当我想生成 5k-10k 的这些对象时,它花费了太多时间。

我目前的解决方案:我生成半填充对象(在启动时)并将它们作为预制件存储在 PrefabManager 中。它们是仅当其 GenerationType 为“固定”时才设置组件值的对象,然后仅使用其他类型的生成填充值。

我的问题:如何通过反射更快地设置值,如果不可能,那么如何更快地获得相同的结果?我还想在启动时保留预制件生成,因为它们可以帮助我实例化对象,因为我不需要创建全新的对象,只需复制预制件并填充它,这对我来说更快。

编辑:添加示例代码。我没有测试它,但是应该很容易理解我想做什么:

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

}

反射慢,但委托执行快。因此,如果您需要经常执行通过反射获得的东西,最好使用反射创建委托并使用该委托。

创建委托非常简单,只要知道 属性 的类型和声明 属性 的类型即可。获取它们的最简单方法是将这些类型作为开放泛型类型中的泛型类型参数。然后,您可以在运行时关闭该类型(在 System.Type 上使用 MakeGenericType)并使用 System.Activator.CreateInstance 实例化关闭的泛型类型。这当然是昂贵的,但您只需要创建一次描述模型属性的对象,然后将其用作任意数量实例的工厂,而无需任何反射调用。

编辑:根据您的示例代码,这是使用属性而不是字段的样子 如果您真的想使用字段(我不鼓励您这样做),创建委托会稍微复杂一些(使用表达式编译器或发出 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();
}