I have two C# classes that have many of the same properties (by name and type). I want to be able to copy all non-null values from an instance of Defect
into an instance of DefectViewModel
. I was hoping to do it with reflection, using GetType().GetProperties()
. I tried the following:
var defect = new Defect();
var defectViewModel = new DefectViewModel();
PropertyInfo[] defectProperties = defect.GetType().GetProperties();
IEnumerable<string> viewModelPropertyNames =
defectViewModel.GetType().GetProperties().Select(property => property.Name);
IEnumerable<PropertyInfo> propertiesToCopy =
defectProperties.Where(defectProperty =>
viewModelPropertyNames.Contains(defectProperty.Name)
);
foreach (PropertyInfo defectProperty in propertiesToCopy)
{
var defectValue = defectProperty.GetValue(defect, null) as string;
if (null == defectValue)
{
continue;
}
// "System.Reflection.TargetException: Object does not match target type":
defectProperty.SetValue(viewModel, defectValue, null);
}
What would be the best way to do this? Should I maintain separate lists of Defect
properties and DefectViewModel
properties so that I can do viewModelProperty.SetValue(viewModel, defectValue, null)
?
Edit: thanks to both Jordão's and Dave's answers, I chose AutoMapper. DefectViewModel
is in a WPF application, so I added the following App
constructor:
public App()
{
Mapper.CreateMap<Defect, DefectViewModel>()
.ForMember("PropertyOnlyInViewModel", options => options.Ignore())
.ForMember("AnotherPropertyOnlyInViewModel", options => options.Ignore())
.ForAllMembers(memberConfigExpr =>
memberConfigExpr.Condition(resContext =>
resContext.SourceType.Equals(typeof(string)) &&
!resContext.IsSourceValueNull
)
);
}
Then, instead of all that PropertyInfo
business, I just have the following line:
var defect = new Defect();
var defectViewModel = new DefectViewModel();
Mapper.Map<Defect, DefectViewModel>(defect, defectViewModel);
Any chance you could have both classes implement an interface that defines the shared properties?
Replace your erroneous line with this:
Your posted code is attempting to set a
Defect
-tied property on aDefectViewModel
object.There are frameworks for this, the one I know of is Automapper:
http://automapper.codeplex.com/
http://www.lostechies.com/blogs/jimmy_bogard/archive/2009/01/22/automapper-the-object-object-mapper.aspx
This is cheap and easy. It makes use of System.Web.Script.Serialization and some extention methods for ease of use:
Here's some similiar but different classes:
And here's the methods in action:
For one thing I would not place that code (somewhere) external but in the constructor of the ViewModel:
And if this is the only class (or one of a few) I would not automate it further but write out the property assignments. Automating it looks nice but there may be more exceptions and special cases than you expect.
In terms of organizing the code, if you don't want an external library like AutoMapper, you can use a mixin-like scheme to separate the code out like this:
This way, all the code to achieve the property-copying is separate from the classes that use it. You just need to reference the mixins in their interface list.
Note that this is not as robust or flexible as AutoMapper, because you might want to copy properties with different names or just some sub-set of the properties. Or it might downright fail if the properties don't provide the necessary getters or setters or their types differ. But, it still might be enough for your purposes.