簡體   English   中英

如何在 c# 中使用反射 SetValue 進行優化?

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

我正在嘗試做的事情:我正在嘗試制作基於組件的對象,這些對象可以使用每個組件值的自定義設置類型的規則輕松創建。

我是怎么做的:我創建了每個組件實現的IComponent接口。 所有組件都需要是結構體,例如:

public struct Weight : IComponent
{
     int weight;
}

每個 Object 僅由組件列表及其值定義。 然后為了使其自定義規則集,我制作了 ObjectSettings ,其中包含通用 class ComponentSetup<T> where T: IComponent的列表。 ComponentSetup是一個 class ,它通過反射獲取 IComponent 中的字段列表,並將它們在 Dicionary 中作為 FieldName 和 GenerationType 配對。 例如:對於 Object“汽車”:

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

對於 Object “人類”:

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

對於 Object“1kg 啞鈴”:

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

為了獲得生成的對象,我使用反射來設置組成列表的組件的值,並以 Object 的形式返回。

這種方法的問題是:當我想生成 5k-10k 這些對象時,它需要太多時間。

到目前為止我的解決方案:我生成半填充對象(在啟動時)並將它們作為預制件存儲在 PrefabManager 中。 它們是僅當其 GenerationType 為“Fixed”時才設置組件值的對象,然后僅使用其他類型的 Generation 填充值。

我的問題:如何通過反射更快地設置值,如果不可能,那么我怎樣才能更快地獲得相同的結果? 我還想在啟動時繼續生成預制件,因為它們可以幫助我實例化對象,因為我不需要創建全新的 object,只需復制預制件並填充它,這在我的情況下更快。

編輯:添加示例代碼。 我沒有對其進行測試,但是應該很容易理解我要做什么:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM