Well, I have a really terrible way to do this.
You can write a method that used reflection (bear with me!) To work out all the properties for a particular type and build a delegate (using Reflection.Emit) to copy properties from that type to another. Then use an anonymous type to make sure that you only need to create a copy delegate once so that it is fast. Then your method will look like this:
public static Expression<Func<Foo, FooEditDto>> EditDtoSelector() { return f => MagicCopier<FooEditDto>.Copy(new { f.PropertyA, f.PropertyB, f.PropertyC, f.PropertyD, f.PropertyC }); }
Nuances here:
- MagicCopier is a generic type, and Copy is a generic method that allows you to explicitly specify a target type, but implicitly specify a source type.
- It uses a projection initializer to infer property names from expressions used to initialize an anonymous type.
I'm not sure if it's really worth it, but it's a pretty funny idea ... I might have to implement it :)
EDIT: MemberInitExpression we could do all this with an expression tree, which makes it a lot easier than CodeDOM. Will try tonight ...
EDIT: Done, and it's actually pretty simple code. Here's the class:
/// <summary> /// Generic class which copies to its target type from a source /// type specified in the Copy method. The types are specified /// separately to take advantage of type inference on generic /// method arguments. /// </summary> public static class PropertyCopy<TTarget> where TTarget : class, new() { /// <summary> /// Copies all readable properties from the source to a new instance /// of TTarget. /// </summary> public static TTarget CopyFrom<TSource>(TSource source) where TSource : class { return PropertyCopier<TSource>.Copy(source); } /// <summary> /// Static class to efficiently store the compiled delegate which can /// do the copying. We need a bit of work to ensure that exceptions are /// appropriately propagated, as the exception is generated at type initialization /// time, but we wish it to be thrown as an ArgumentException. /// </summary> private static class PropertyCopier<TSource> where TSource : class { private static readonly Func<TSource, TTarget> copier; private static readonly Exception initializationException; internal static TTarget Copy(TSource source) { if (initializationException != null) { throw initializationException; } if (source == null) { throw new ArgumentNullException("source"); } return copier(source); } static PropertyCopier() { try { copier = BuildCopier(); initializationException = null; } catch (Exception e) { copier = null; initializationException = e; } } private static Func<TSource, TTarget> BuildCopier() { ParameterExpression sourceParameter = Expression.Parameter(typeof(TSource), "source"); var bindings = new List<MemberBinding>(); foreach (PropertyInfo sourceProperty in typeof(TSource).GetProperties()) { if (!sourceProperty.CanRead) { continue; } PropertyInfo targetProperty = typeof(TTarget).GetProperty(sourceProperty.Name); if (targetProperty == null) { throw new ArgumentException("Property " + sourceProperty.Name + " is not present and accessible in " + typeof(TTarget).FullName); } if (!targetProperty.CanWrite) { throw new ArgumentException("Property " + sourceProperty.Name + " is not writable in " + typeof(TTarget).FullName); } if (!targetProperty.PropertyType.IsAssignableFrom(sourceProperty.PropertyType)) { throw new ArgumentException("Property " + sourceProperty.Name + " has an incompatible type in " + typeof(TTarget).FullName); } bindings.Add(Expression.Bind(targetProperty, Expression.Property(sourceParameter, sourceProperty))); } Expression initializer = Expression.MemberInit(Expression.New(typeof(TTarget)), bindings); return Expression.Lambda<Func<TSource,TTarget>>(initializer, sourceParameter).Compile(); } }
And calling him:
TargetType target = PropertyCopy<TargetType>.CopyFrom(new { First="Foo", Second="Bar" });
Jon skeet
source share