C # - копирование значений свойств из одного экземпляра в другой, разные классы - PullRequest
4 голосов
/ 31 августа 2010

У меня есть два класса C #, которые имеют много одинаковых свойств (по имени и типу).Я хочу иметь возможность скопировать все ненулевые значения из экземпляра Defect в экземпляр DefectViewModel.Я надеялся сделать это с отражением, используя GetType().GetProperties().Я попробовал следующее:

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

Каков наилучший способ сделать это?Должен ли я вести отдельные списки Defect свойств и DefectViewModel свойств, чтобы я мог viewModelProperty.SetValue(viewModel, defectValue, null)?

Редактировать: благодаря обоим Иордана и Дейв отвечает, я выбрал AutoMapper.DefectViewModel находится в приложении WPF, поэтому я добавил следующий конструктор App:

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

Затем, вместо всего этого PropertyInfo бизнеса, у меня просто есть следующая строка:

var defect = new Defect();
var defectViewModel = new DefectViewModel();
Mapper.Map<Defect, DefectViewModel>(defect, defectViewModel);

Ответы [ 7 ]

8 голосов
/ 31 августа 2010

Взгляните на AutoMapper .

3 голосов
/ 31 августа 2010
2 голосов
/ 21 августа 2012

Это дешево и просто.Для простоты использования он использует System.Web.Script.Serialization и некоторые методы расширения:

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

Вот несколько похожих, но разных классов:

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

действие:

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
2 голосов
/ 31 августа 2010

С точки зрения организации кода, если вам не нужна внешняя библиотека, такая как AutoMapper, вы можете использовать mixin-like схему для разделения кода следующим образом:

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

Таким образом, весь код для копирования свойств отделен от классов, которые его используют. Вам просто нужно сослаться на миксины в их списке интерфейсов.

Обратите внимание, что это не так надежно и гибко, как AutoMapper, потому что вы можете скопировать свойства с разными именами или просто подмножество свойств. Или это может привести к сбою, если свойства не предоставляют необходимые методы получения или установки или их типы различаются. Но этого все еще может быть достаточно для ваших целей.

2 голосов
/ 31 августа 2010

Замените свою ошибочную строку следующим:

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

Ваш опубликованный код пытается установить свойство Defect на объекте DefectViewModel.

1 голос
/ 31 августа 2010

С одной стороны, я бы не размещал этот код (где-то) снаружи, а в конструкторе ViewModel:

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

И если бы это был единственный класс (или один из немногих), я бы не сталавтоматизируйте это далее, но выпишите имущественные назначения.Автоматизация выглядит красиво, но может быть больше исключений и особых случаев, чем вы ожидаете.

0 голосов
/ 31 августа 2010

Есть ли вероятность, что оба класса реализуют интерфейс, определяющий общие свойства?

...