我想编写一个函数,将相同的属性从一个对象复制到另一个对象,但同时它可以快速工作。这里有什么:
static class Copy
{
private static readonly ConcurrentDictionary<Type, ConcurrentDictionary<string, PropertyInfo>> PropertiesDictionaries
= new ConcurrentDictionary<Type, ConcurrentDictionary<string, PropertyInfo>>();
public static void Сopyfields(object source, object target)
{
var sourceType = source.GetType();
var targetType = target.GetType();
var sourceProperties = GetProperties(sourceType);
var targetPropertyes = GetProperties(targetType);
foreach (var targetProperty in targetPropertyes)
{
if (sourceProperties.TryGetValue(targetProperty.Key, out var sourceProperty))
{
targetProperty.Value.SetValue(target, sourceProperty.GetValue(source));
}
}
}
private static ConcurrentDictionary<string, PropertyInfo> GetProperties(Type objType)
{
if (!PropertiesDictionaries.TryGetValue(objType, out var propertiesInfoDictionary))
{
var infos = objType.GetProperties();
propertiesInfoDictionary = new ConcurrentDictionary<string, PropertyInfo>();
foreach (var propertyInfo in infos)
{
propertiesInfoDictionary.GetOrAdd(propertyInfo.Name, propertyInfo);
}
PropertiesDictionaries.GetOrAdd(objType, propertiesInfoDictionary);
}
return propertiesInfoDictionary;
}
}
如何重写此函数以创建表达式并为每种类型组合仅使用一次反射?
这是最简单的选择:
它不检查可访问性,不查看私有/受保护,也不检查类型兼容性。如果无法复制同名属性,则在创建映射器时会抛出异常。如果目标属性不可写,则在创建映射器时会抛出异常。不匹配的属性会被静默忽略。如果派生类的属性在名称上覆盖了基类的属性,则忽略基类的属性(您还能做什么?)。静态属性被忽略(这似乎是正确的)。显式接口实现的属性将被忽略(因为我还没有弄清楚如何处理它们)。
像这样使用:
正常工作且不会崩溃的选项:
解释。
我们先来看看函数
GetVisibleProperties。我们需要获取所有属性的列表。这通常用一个简单的 来完成type.GetProperties(),但是如果我们有多个同名的属性呢?如果基类中的属性被覆盖,就会发生这种情况new,如上一个示例所示。如何只获得最新的财产?要做到这一点,让我们做这个技巧:让我们遍历从我们的类型到的基本类型链
object,并且在每一步它将只添加在该类型中定义的属性,并且只添加那些名称尚未与我们相遇的属性. 为此,我们需要获得一系列基本类型。最简单的方法是递归:将我们自己的类型添加到基类的链顶部。结果很简单:BindingFlags.Public好的,那么我们遵循这个链,对于每种类型,我们只获得公共属性BindingFlags.Instance(BindingFlags.DeclaredOnly对于它们中的每一个,我们检查是否已经存在同名的属性,并且仅当还没有同名的属性时才将新属性添加到结果中。与
GetVisibleProperties一切。现在,主要功能:CreateMapper.我们将类型作为泛型参数传递,以便我们可以返回一个类型化的委托
Action<P, Q>。首先,我们使用已经解析的GetVisibleProperties. 接下来,我们取名称集的交集:这些是两种类型中的属性的名称 (commonProperties)。我还按字母顺序对它们进行排序,以便顺序相同。我们通过
System.Linq.Expressions,它允许您Expression在代码中构造表达式(如 )。这是一种标准的代码生成技术,我们在网站上有很多使用这种技术的答案。所以这里是辅助函数
CreateCopyProperty。它创建了表达式的类似物to.Prop1 = from.Prop1;。我们预先定义了Expression-variablesfromVar并toVar对应于类型P和Q命名的“真实”变量from和to。Expression.Property(toVar, targetProperties[name])- 这是一个完整的模拟to.Prop1,其中属性是Prop1通过定义的PropertyInfo(这对于我们可能有重叠名称的情况是必要的)。Expression.Assign当然,创建分配的代码。例如,如果属性的类型不兼容,或者目标属性没有设置器,则创建表达式可能会引发异常。有可能不捕获此异常,但不清楚哪个属性是“应受责备”,所以我添加了一个 try / catch 块,并在新异常的文本中提到了有问题的属性的名称.好的,接下来。我们有一组属性名称 (
commonProperties),使用Select它来创建一组Expression's。这个集合需要打包成一个 usingExpression.Block,它创建了一个花括号块的模拟:现在我们从这个块中创建一个 lambda:
在 的帮助下
Expression.Lambda。最后一个技巧是调用
Compile,它Expression编译成一个真正的函数,我们返回给用户。