毕竟,我知道AutoMapper,我不想使用它。因为我正在学习C#并且我想深入了解它。所以我正在尝试自己解决这个问题(如下所述)。
然而,我正在尝试创建一个属性复制器,以将一个类型的属性的值复制到另一个类型,如果该属性具有相同的名称和类型,并且在源中可读,在目标中可写。我使用的是type.GetProperties()
方法。示例方法如下:
static void Transfer(object source, object target) {
var sourceType = source.GetType();
var targetType = target.GetType();
var sourceProps = sourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
var targetProps = (from t in targetType.GetProperties()
where t.CanWrite
&& (t.GetSetMethod().Attributes & MethodAttributes.Static) == 0
select t).ToList();
foreach(var prop in sourceProps) {
var value = prop.GetValue(source, null);
var tProp = targetProps
.FirstOrDefault(p => p.Name == prop.Name &&
p.PropertyType.IsAssignableFrom(prop.PropertyType));
if(tProp != null)
tProp.SetValue(target, value, null);
}
}
它是有效的,但我在SO上读到了一个答案,即使用System.Reflection.Emit
和ILGenerator
以及后绑定委托更快,性能更高。但没有更多的解释或任何联系。你能帮我理解加速这段代码的方法吗?或者你能给我推荐一些关于Emit
、ILGenerator
和后期绑定代表的链接吗?或者你认为有什么能帮助我的吗?
竞争者Q:
我从@svick的回答中学到了很多东西。但现在,如果我想把它用作一个开放的泛型方法,我该怎么做呢?像这样的东西:
public TTarget Transfer<TSource, TTarget>(TSource source) where TTarget : class, new() { }
或扩展:
public static TTarget Transfer<TSource, TTarget>(this TSource source) where TTarget : class, new() { }
您可以使用Reflection.Emit来实现这一点,但使用Expression
s通常要容易得多,并且它可以提供基本相同的性能。请记住,只有当您缓存编译后的代码时,性能优势才存在,例如在Dictionary<Tuple<Type, Type>, Action<object, object>>
中,我在这里不做这件事。
static void Transfer(object source, object target)
{
var sourceType = source.GetType();
var targetType = target.GetType();
var sourceParameter = Expression.Parameter(typeof(object), "source");
var targetParameter = Expression.Parameter(typeof(object), "target");
var sourceVariable = Expression.Variable(sourceType, "castedSource");
var targetVariable = Expression.Variable(targetType, "castedTarget");
var expressions = new List<Expression>();
expressions.Add(Expression.Assign(sourceVariable, Expression.Convert(sourceParameter, sourceType)));
expressions.Add(Expression.Assign(targetVariable, Expression.Convert(targetParameter, targetType)));
foreach (var property in sourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (!property.CanRead)
continue;
var targetProperty = targetType.GetProperty(property.Name, BindingFlags.Public | BindingFlags.Instance);
if (targetProperty != null
&& targetProperty.CanWrite
&& targetProperty.PropertyType.IsAssignableFrom(property.PropertyType))
{
expressions.Add(
Expression.Assign(
Expression.Property(targetVariable, targetProperty),
Expression.Convert(
Expression.Property(sourceVariable, property), targetProperty.PropertyType)));
}
}
var lambda =
Expression.Lambda<Action<object, object>>(
Expression.Block(new[] { sourceVariable, targetVariable }, expressions),
new[] { sourceParameter, targetParameter });
var del = lambda.Compile();
del(source, target);
}
如果你有这个,写你的通用方法很简单:
public TTarget Transfer<TSource, TTarget>(TSource source)
where TTarget : class, new()
{
var target = new TTarget();
Transfer(source, target);
return target;
}
让主worker方法也通用并创建Action<TSource, TTarget>
,甚至让它直接创建对象并使用Func<TSource, TTarget>
,都是有意义的。但是,如果按照我的建议添加缓存,则意味着您必须使用类似Dictionary<Tuple<Type, Type>, Delegate>
的东西,并在从缓存中检索委托后将其强制转换为正确的类型。
您可以考虑只获取与目标匹配的属性(按名称)。这将大大简化您的代码。
foreach (var property in sourceType.GetProperties( BindingFlags.Public | BindingFlags.Instance))
{
var targetProperty = targetType.GetProperty( property.Name, BindingFlags.Public | BindingFlags.Instance );
if (targetProperty != null
&& targetProperty.CanWrite
&& targetProperty.PropertyType.IsAssignableFrom(property.PropertyType))
{
targetProperty.SetValue( target, property.GetValue(source, null), null );
}
}
C#反射IL-了解如何复制值
问题是关于克隆,因此源对象的类型与目标对象的类型相同(而据我所知,源和目标中可以有不同的类型),但这仍然值得分析。
我写了一篇关于如何做到这一点的博客文章(只有葡萄牙语,但你可以阅读代码)
http://elemarjr.net/2012/02/27/um-helper-para-shallow-cloning-emitting-em-c/
您可以从以下位置获取代码:
https://github.com/ElemarJR/FluentIL/blob/master/demos/Cloning/src/Cloning/Cloning/ILCloner.cs
我认为使用反射。发射比需要的更难。因此,我写了一个开源库,名为FluentIL(www.FluentIL.org),让它变得更容易。我在这里用过。
[]s