将 [name,value] 字符串值映射到类而不进行反射

Posted

技术标签:

【中文标题】将 [name,value] 字符串值映射到类而不进行反射【英文标题】:Map [name,value] string values to class without reflection 【发布时间】:2021-08-04 15:26:57 【问题描述】:

我在使用反射将字符串属性名称和字符串属性值映射到类时遇到了巨大的性能问题。

我现在的问题:

  public class Person

    public string Property1  get; set; 

    public string Property2  get; set; 

    public string Property3  get; set; 

    public string Property4  get; set; 

    // My class has around 100 properties
    public string Property100  get; set; 

我正在使用反射将键值对集合映射到类

["Property1": "some value", "Property2": "something else","Property3","Property4","value" ....."Property100","val"]

我现在正在使用反射映射大约 10 000 个类实例,性能可以说有点糟糕。

任何消除反射的想法将不胜感激。

【问题讨论】:

能否提供相关代码 您对数据有控制权吗?如果是这样,您可以使用 json 或 xml 之类的序列化程序,而不是自己滚动。他们仍然使用反射,但表现良好。 【参考方案1】:

我看到两个选项,如果您需要避免对此类任务进行反射(当代码可以以编程方式生成时)。

首先是

Expressions

我经常使用它,例如看到有人写这样的
public class A

    public Prop1 ...
    ....
    public Prop100
    public override ToString() => $"nameof(Prop1)=Prop1;...";

对于所有 100 个属性也是如此,并且始终手动执行此操作。

使用 Expression 可以轻松实现自动化,您只需为 String.Concat 生成 Expression 并在那里传递属性和名称列表。

对于您的示例,尚不清楚您的数据是什么。你如何在列表中查找? 假设有一个字典(您可以将元组列表转换为字典),并且所有属性也是字符串。

然后我们需要像这样生成一个列表赋值表达式 if(data.ContainsKey("Prop1")) result.Prop1 = data["Prop1"];

而且代码会很复杂,反正看起来是这样的

   private static class CompiledDelegate<T>
    
        public static Action<T, Dictionary<string, string>> initObject;

         static  CompiledDelegate()
        
            var i = Expression.Parameter(typeof(Dictionary<string, string>), "i");
            var v = Expression.Parameter(typeof(T), "v");

            var propertyInfos = typeof(T).GetProperties().ToArray();

            var t = new Dictionary<string, string>();

            var contains = typeof(Dictionary<string, string>).GetMethod(nameof(Dictionary<string, string>.ContainsKey));
            var getter = typeof(Dictionary<string, string>).GetProperties().First(x => x.GetIndexParameters().Length > 0);

            var result = new List<Expression>();

            foreach (var propertyInfo in propertyInfos)
            
                var cst = Expression.Constant(propertyInfo.Name);

                var assignExpression =

                    Expression.IfThen(Expression.Call(i, contains, cst),
                    Expression.Assign(Expression.PropertyOrField(v, propertyInfo.Name), Expression.MakeIndex(i, getter, new[]  cst )));

                result.Add(assignExpression);
            




            var block = Expression.Block(result);

            initObject = Expression.Lambda<Action<T, Dictionary<string, string>>>(block, new ParameterExpression[]  v, i ).Compile();
        
    

这是一个例子,如果有非字符串属性会失败。

而且可以这样使用

    static void Main(string[] args)
    
        var tst = new Test();

        CompiledDelegate<Test>.initObject(tst, new Dictionary<string, string>
        
             "S3", "Value3" ,
             "S2", "Value2" ,
        );

        CompiledDelegate<Test>.initObject(tst, new Dictionary<string, string>
        
             "S3", "Value3" ,
             "S1", "Value1" ,
        );

        Console.ReadKey();

    

第二个选项实际上应该是理想的实现方式

使用源代码生成器

我认为这些事情必须在构建时完成。

msdn 上有很多文章,例如示例。但结果证明实现起来并不容易,即使只是一个示例。

我可以说,它对我不起作用,而我试图根据样本来做。 为了让它工作,我不得不将 TargetFramework 更改为 netstandard2.0,做点别的......

但毕竟在 build 是绿色的时候,Visual Studio 还是会报错。

好的,VS重启后它就消失了,但是看起来不太好用。

所以,这是一个生成器,它为每个具有属性的类创建一个转换器。 它又是一个样本,它并没有检查很多东西。

    [Generator]
public class ConverterGenerator : ISourceGenerator

    private static string mytemplate = @"using System.Collections.Generic;
                                        using 2;
                                        namespace GeneratedConverters
                                        
                                            public static class 0Converter
                                            
                                                public static 0 Convert(Dictionary<string, string> data)
                                                
                                                    var result = new 0();
                                                    1


                                                    return result;
                                                
                                            
                                        ";

    public static string GetNamespaceFrom(SyntaxNode s)
    
        if (s.Parent is NamespaceDeclarationSyntax namespaceDeclarationSyntax)
        
            return namespaceDeclarationSyntax.Name.ToString();
        
        if (s.Parent == null)
            return "";

        return GetNamespaceFrom(s.Parent);
    

    public void Execute(GeneratorExecutionContext context)
    
        GetMenuComponents(context, context.Compilation);
    

    private static void GetMenuComponents(GeneratorExecutionContext context, Compilation compilation)
    
        var allNodes = compilation.SyntaxTrees.SelectMany(s => s.GetRoot().DescendantNodes());
        var allClasses = allNodes.Where(d => d.IsKind(SyntaxKind.ClassDeclaration)).OfType<ClassDeclarationSyntax>();

        var classes = allClasses
            .Where(c => c.AttributeLists.SelectMany(a => a.Attributes).Select(a => a.Name).Any(s => s.ToString().Contains("DictionaryConverter")))
            .ToImmutableArray();

        foreach (var item in classes.Distinct().Take(1))
        
            context.AddSource(item.Identifier.Text + "Converter", String.Format(mytemplate, item.Identifier.Text, SourceText.From(GenerateProperties(item)), GetNamespaceFrom(item)));
        
    

    private static string GenerateProperties(ClassDeclarationSyntax s)
    
        var properties = s.Members.OfType<PropertyDeclarationSyntax>();

        return String.Join(Environment.NewLine,
            properties.Select(p =>
            
                var name = p.Identifier.Text;
                return $"if(data.ContainsKey(\"name\")) result.name = data[\"name\"];";
            ));
    

    public void Initialize(GeneratorInitializationContext context)
    
    

    static void Main(string[] args)
    
        var t1 = GeneratedConverters.TestConverter.Convert(new Dictionary<string, string>
        
             "S3", "Value3" ,
             "S2", "Value2" ,
        );
    

【讨论】:

【参考方案2】:

没有反射的最佳性能是手动映射。 您的键/值对集合似乎是常规 JSON。因此,您可以使用 JSON.NET 中的 JSONTextReader 并读取字符串。然后手动将 JSON 属性映射到类属性。

像这样:

JsonTextReader reader = new JsonTextReader(new StringReader(jsonString));

while (reader.Read())

    if (reader.Value != null)
    
        // check reader.Value.ToString() and assign to correct class property

    

更多信息可以在 JSON.NET 网站上找到:https://www.newtonsoft.com/json/help/html/ReadingWritingJSON.htm

【讨论】:

以上是关于将 [name,value] 字符串值映射到类而不进行反射的主要内容,如果未能解决你的问题,请参考以下文章

为啥方法局部静态变量绑定到类而不是实例?

将 StatefulWidget 数据传递给 State 类而不使用构造函数

将firebase响应映射到类

如何创建 Python Pyramid 视图类而不需要为每个方法指定“名称”

将参数从jsp页面传递给java类而不使用servlet [重复]

是否可以将方法添加到现有(内置)ActionScript Flash 类而不扩展它并创建一个新类?