views:

160

answers:

5

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<ChangeRequest, ChangeRequestViewModel>(defect, defectViewModel);
A: 

Any chance you could have both classes implement an interface that defines the shared properties?

Steven Sudit
I thought about that. `Defect` is defined in an external library and I would prefer to not have to modify it, because adding an interface for these particular shared properties really only makes sense in the context of the library where `DefectViewModel` is.
Sarah Vessels
That makes sense. Sounds like you're stuck with one of the reflection-based solutions. I recommend Henk's advice about using a constructor, though.
Steven Sudit
+6  A: 

Take a look at AutoMapper.

Jordão
+2  A: 

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

Dave Swersky
+2  A: 

Replace your erroneous line with this:

PropertyInfo targetProperty = defectViewModel.GetType().GetProperty(defectProperty.Name);
targetProperty.SetValue(viewModel, defectValue, null);

Your posted code is attempting to set a Defect-tied property on a DefectViewModel object.

+1  A: 

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:

class Program {
  static void Main(string[] args) {
    var d = new Defect() { Category = "bug", Status = "open" };
    var m = new DefectViewModel();
    m.CopyPropertiesFrom(d);
    Console.WriteLine("{0}, {1}", m.Category, m.Status);
  }
}

// compositions

class Defect : MPropertyGettable {
  public string Category { get; set; }
  public string Status { get; set; }
  // ...
}

class DefectViewModel : MPropertySettable {
  public string Category { get; set; }
  public string Status { get; set; }
  // ...
}

// quasi-mixins

public interface MPropertyEnumerable { }
public static class PropertyEnumerable {
  public static IEnumerable<string> GetProperties(this MPropertyEnumerable self) {
    return self.GetType().GetProperties().Select(property => property.Name);
  }
}

public interface MPropertyGettable : MPropertyEnumerable { }
public static class PropertyGettable {
  public static object GetValue(this MPropertyGettable self, string name) {
    return self.GetType().GetProperty(name).GetValue(self, null);
  }
}

public interface MPropertySettable : MPropertyEnumerable { }
public static class PropertySettable {
  public static void SetValue<T>(this MPropertySettable self, string name, T value) {
    self.GetType().GetProperty(name).SetValue(self, value, null);
  }
  public static void CopyPropertiesFrom(this MPropertySettable self, MPropertyGettable other) {
    self.GetProperties().Intersect(other.GetProperties()).ToList().ForEach(
      property => self.SetValue(property, other.GetValue(property)));
  }
}

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.

Jordão