将 [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" },
});
}
我在使用反射将字符串 属性 名称和字符串 属性 值映射到 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" },
});
}