4

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);
Community
  • 1
  • 1
Sarah Vessels
  • 30,930
  • 33
  • 155
  • 222

7 Answers7

8

Take a look at AutoMapper.

Jordão
  • 55,340
  • 13
  • 112
  • 144
3

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
  • 34,502
  • 9
  • 78
  • 118
2

This is cheap and easy. It makes use of System.Web.Script.Serialization and some extention methods for ease of use:

public static class JSONExts
{
    public static string ToJSON(this object o)
    {
        var oSerializer = new System.Web.Script.Serialization.JavaScriptSerializer();
        return oSerializer.Serialize(o);
    }

    public static List<T> FromJSONToListOf<T>(this string jsonString)
    {
        var oSerializer = new System.Web.Script.Serialization.JavaScriptSerializer();
        return oSerializer.Deserialize<List<T>>(jsonString);
    }

    public static T FromJSONTo<T>(this string jsonString)
    {
        var oSerializer = new System.Web.Script.Serialization.JavaScriptSerializer();
        return oSerializer.Deserialize<T>(jsonString);
    }

    public static T1 ConvertViaJSON<T1>(this object o)
    {
        return o.ToJSON().FromJSONTo<T1>();
    }
}

Here's some similiar but different classes:

public class Member
        {
            public string Name { get; set; }
            public int Age { get; set; }
            public bool IsCitizen { get; set; }
            public DateTime? Birthday { get; set; }

            public string PetName { get; set; }
            public int PetAge { get; set; }
            public bool IsUgly { get; set; }
        }

        public class MemberV2
        {
            public string Name { get; set; }
            public int Age { get; set; }
            public bool IsCitizen { get; set; }
            public DateTime? Birthday { get; set; }

            public string ChildName { get; set; }
            public int ChildAge { get; set; }
            public bool IsCute { get; set; }
        } 

And here's the methods in action:

var memberClass1Obj = new Member {
                Name = "Steve Smith",
                Age = 25,
                IsCitizen = true,
                Birthday = DateTime.Now.AddYears(-30),
                PetName = "Rosco",
                PetAge = 4,
                IsUgly = true,
            };

            string br = "<br /><br />";
            Response.Write(memberClass1Obj.ToJSON() + br); // just to show the JSON

            var memberClass2Obj = memberClass1Obj.ConvertViaJSON<MemberV2>();
            Response.Write(memberClass2Obj.ToJSON()); // valid fields are filled
Graham
  • 3,217
  • 1
  • 27
  • 29
  • not sure why this wasn't accepted as an answer. I know it has limitations as far as nested properties (IList etc) but it works and is a very simple answer. Like it. – hal9000 Nov 10 '14 at 21:50
2

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.

2

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
  • 55,340
  • 13
  • 112
  • 144
1

For one thing I would not place that code (somewhere) external but in the constructor of the ViewModel:

class DefectViewModel
{
    public DefectViewModel(Defect source)  { ... }
}

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.

H H
  • 263,252
  • 30
  • 330
  • 514
  • Not sure I agree about avoiding automation, but I certainly agree about putting the code into the constructor. – Steven Sudit Aug 31 '10 at 18:07
  • What I intended to mean wasn't avoiding but more 'not over-using' – H H Aug 31 '10 at 19:06
  • Then I can't argue with that. As much as AutoMapper is an interesting general solution, it does seem to be very complex if all we really need is a short list of hardcoded assignments that rarely changes. – Steven Sudit Aug 31 '10 at 19:29
0

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

Steven Sudit
  • 19,391
  • 1
  • 51
  • 53
  • 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 Aug 31 '10 at 16:09
  • 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 Aug 31 '10 at 18:06