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

Map [name,value] string values to class without reflection

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

我现在的问题:

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

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

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

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

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

没有反射的最佳性能是手动映射。 看来你的 key/value 对 collection 是正常的 JSON。因此,您可以使用 JSON.NET 中的 JSONTextReader 并读取字符串。然后手动将 JSON 属性映射到 class 属性。

像这样:

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

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

首先是

表达式

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

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 重启后它就消失了,但看起来还是不太好用。

所以,这是一个生成器,它为每个具有属性的 class 创建一个转换器。 它又是一个示例,它不会检查很多东西。

    [Generator]
public class ConverterGenerator : ISourceGenerator
{
    private static string mytemplate = @"using System.Collections.Generic;
                                        using {2};
                                        namespace GeneratedConverters
                                        {{
                                            public static class {0}Converter
                                            {{
                                                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" },
        });
    }