问题
Before all, I know about AutoMapper, and I don't want to use it. Because I'm learning C# and I want to receive a deep view of it. So I'm trying to do this issue (explained below) myself.
However, I'm trying to create a property copier to cope values of one type's properties to another one, if the property has the same name and type and is readable from source and writable in target. I'm using type.GetProperties()
method. Sampled method is here:
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);
}
}
It works, but I read an answer at SO, that using System.Reflection.Emit
and ILGenerator
and late-bound delegates are more quickly and have a higher performance. But there was not more explanation or any link. Can you help me to understanding ways to speed up this code? or can you suggest me some links about Emit
, ILGenerator
, and late-bound delegates please? Or anything you think will help me to subject?
COMPELETE Q:
I understand and learn many things from @svick's answer. But now, if I want to use it as an open generic method, how can I do it? something like this:
public TTarget Transfer<TSource, TTarget>(TSource source) where TTarget : class, new() { }
or an extension:
public static TTarget Transfer<TSource, TTarget>(this TSource source) where TTarget : class, new() { }
回答1:
You could use Reflection.Emit to do this, but it's usually much easier to use Expression
s and it gives you basically the same performance. Keep in mind that the performance benefit is there only if you cache the compiled code, for example in Dictionary<Tuple<Type, Type>, Action<object, object>>
, which I'm not doing here.
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);
}
If you have this, writing your generic method is simpple:
public TTarget Transfer<TSource, TTarget>(TSource source)
where TTarget : class, new()
{
var target = new TTarget();
Transfer(source, target);
return target;
}
It could make sense to make the main worker method generic too and create Action<TSource, TTarget>
, or even let it directly create the object and use Func<TSource, TTarget>
. But if added caching as I suggested, it would mean you would have to use something like Dictionary<Tuple<Type, Type>, Delegate>
and cast the delegate to the right type after retrieving it from the cache .
回答2:
You might consider only getting the properties (by name) that match on the target. That will significantly simplify your code.
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 );
}
}
回答3:
C# Reflection IL - Understanding how values are copied
The question is about cloning so the type of the source object is the same as the type of the target object (while as I understand you can have different types in source and target) but still this could be worth of analyzing.
回答4:
I wrote a blog post about how to do it (portuguese only, but you could read the code)
http://elemarjr.net/2012/02/27/um-helper-para-shallow-cloning-emitting-em-c/
You can get the code from:
https://github.com/ElemarJR/FluentIL/blob/master/demos/Cloning/src/Cloning/Cloning/ILCloner.cs
I think that to use Reflection.Emit is harder than needed. So, I wrote a open-source library, called FluentIL (www.fluentil.org) to make it easier. I used it here.
[]s
来源:https://stackoverflow.com/questions/9762190/transferring-one-object-properties-values-to-another-one