C# - 将属性值从一个实例复制到另一个实例,不同的类

4
我有两个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)吗?

编辑:感谢JordãoDave的回答,我选择了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

3

2

将您错误的行替换为以下内容:

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

您上传的代码试图在一个 DefectViewModel 对象上设置一个与 Defect 相关的属性。


2
关于代码的组织,如果您不想使用像AutoMapper这样的外部库,可以使用类似mixin的方案将代码分离出来,如下所示:
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)));
  }
}

这样,实现属性复制的所有代码都与使用它的类分开。您只需要在接口列表中引用 mixins。
请注意,这不像 AutoMapper 那样健壮或灵活,因为您可能想要复制具有不同名称的属性或仅某些子集的属性。或者,如果属性未提供必要的 getter 或 setter,或者它们的类型不同,则可能根本无法正常运行。但是,这仍然可能足以满足您的需求。

2

这很便宜且易于操作。它利用了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

不确定为什么这个答案没有被接受。我知道它在嵌套属性(如IList等)方面有限制,但它可以工作,并且是一个非常简单的答案。我喜欢它。 - hal9000

1

首先,我不会将该代码(某处)放在外部,而是放在ViewModel的构造函数中:

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

如果这是唯一的类(或其中之一),我不会进一步自动化,而是将属性分配写出来。自动化看起来很好,但你可能会遇到更多的异常和特殊情况。


我不确定是否同意避免自动化,但我肯定同意将代码放入构造函数中。 - Steven Sudit
我想表达的意思不是避免,而是更多地“不过度使用”。 - H H
那我无法反驳。虽然AutoMapper是一个有趣的通用解决方案,但如果我们真正需要的只是一个很少更改的硬编码分配的简短列表,它似乎非常复杂。 - Steven Sudit

0

有没有可能让这两个类都实现一个定义共享属性的接口?


我考虑过这个问题。Defect在外部库中定义,我不想修改它,因为为这些特定的共享属性添加接口只有在DefectViewModel所在的库的上下文中才有意义。 - Sarah Vessels
听起来很有道理。看来你被限制在基于反射的解决方案上了。不过我建议你采纳Henk的建议,使用构造函数。 - Steven Sudit

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接