繁体   English   中英

Map [name,value] 字符串值到 class 没有反射

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

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

我现在的问题:

  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 实例,性能可以说有点糟糕。

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

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

首先是

表达式

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

public class A { public Prop1... .... public Prop100 public override ToString() => $"{nameof(Prop1)}={Prop1};...";

所以对于所有 100 个属性,并且总是手动执行此操作。

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

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

然后我们需要生成一个像这样的列表赋值表达式 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" }, }); }

没有反射的最佳性能将是手动映射。 看来您的键/值对集合是常规的 JSON。 因此,您可以使用 JSON.NET 中的 JSONTextReader 并读取字符串。 然后手动将 map 的 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

暂无
暂无

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

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