开发者

C# - copying property values from one instance to another, different classes

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);


Take a look at AutoMapper.


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


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.


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.


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


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.


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

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜