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

У меня есть два класса 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);

person Sarah Vessels    schedule 31.08.2010    source источник


Ответы (7)


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

person Jordão    schedule 31.08.2010

Для этого есть фреймворки, из которых я знаю Automapper:

http://automapper.codeplex.com/

http://www.lostechies.com/blogs/jimmy_bogard/archive/2009/01/22/automapper-the-object-object-mapper.aspx

person Dave Swersky    schedule 31.08.2010

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

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

Опубликованный вами код пытается установить свойство Defect-tied для объекта DefectViewModel.

person Community    schedule 31.08.2010

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

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, потому что вы можете захотеть скопировать свойства с другими именами или просто некоторые подмножества свойств. Или он может полностью потерпеть неудачу, если свойства не предоставляют необходимые геттеры или сеттеры или их типы различаются. Но этого может хватить для ваших целей.

person Jordão    schedule 31.08.2010

Это дешево и просто. Он использует 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
person Graham    schedule 21.08.2012
comment
не уверен, почему это не было принято в качестве ответа. Я знаю, что у него есть ограничения в отношении вложенных свойств (IList и т. Д.), Но он работает и является очень простым ответом. Нравится. - person hal9000; 11.11.2014

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

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

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

person Henk Holterman    schedule 31.08.2010
comment
Не уверен, что я согласен избегать автоматизации, но я определенно согласен с помещением кода в конструктор. - person Steven Sudit; 31.08.2010
comment
Я хотел иметь в виду не избегать, а скорее «не злоупотреблять» - person Henk Holterman; 31.08.2010
comment
Тогда я не могу с этим поспорить. Хотя AutoMapper является интересным общим решением, оно может показаться очень сложным, если все, что нам действительно нужно, - это короткий список жестко запрограммированных назначений, которые редко меняются. - person Steven Sudit; 31.08.2010

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

person Steven Sudit    schedule 31.08.2010
comment
Я думала об этом. Defect определен во внешней библиотеке, и я бы предпочел не изменять его, потому что добавление интерфейса для этих конкретных общих свойств действительно имеет смысл только в контексте библиотеки, где находится DefectViewModel. - person Sarah Vessels; 31.08.2010
comment
В этом есть смысл. Похоже, вы застряли с одним из решений, основанных на отражении. Тем не менее, я рекомендую совет Хенка по использованию конструктора. - person Steven Sudit; 31.08.2010