【问题标题】:C# - copying property values from one instance to another, different classesC# - 将属性值从一个实例复制到另一个不同的类
【发布时间】:2011-04-06 08:52:32
【问题描述】:

我有两个 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ão'sDave's 的回答,我选择了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);

【问题讨论】:

    标签: c# reflection properties mapping


    【解决方案1】:

    这既便宜又容易。它利用了 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 等)方面有局限性,但它可以工作并且是一个非常简单的答案。喜欢。
    【解决方案2】:

    在组织代码方面,如果您不想要 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)));
      }
    }
    

    这样,实现属性复制的所有代码都与使用它的类分开。你只需要在他们的接口列表中引用 mixins。

    请注意,这不如 AutoMapper 强大或灵活,因为您可能希望复制具有不同名称的属性或只是属性的某些子集。或者,如果属性没有提供必要的 getter 或 setter 或者它们的类型不同,它可能会彻底失败。但是,对于您的目的来说,它仍然可能已经足够了。

    【讨论】:

      【解决方案3】:

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

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

      如果这是唯一的类(或少数类之一),我不会进一步自动化它,而是写出属性分配。自动化看起来不错,但可能会有比您预期的更多的例外和特殊情况。

      【讨论】:

      • 不确定我是否同意避免自动化,但我当然同意将代码放入构造函数中。
      • 我的意思不是避免,而是更多“不要过度使用”
      • 那我没法反驳。尽管 AutoMapper 是一个有趣的通用解决方案,但如果我们真正需要的是一个很少更改的硬编码分配的简短列表,它似乎确实非常复杂。
      【解决方案4】:

      用这个替换你的错误行:

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

      您发布的代码正在尝试在 DefectViewModel 对象上设置 Defect-tied 属性。

      【讨论】:

        【解决方案5】:
        【解决方案6】:

        看看AutoMapper

        【讨论】:

          【解决方案7】:

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

          【讨论】:

          • 我想过。 Defect 是在外部库中定义的,我宁愿不必修改它,因为为这些特定的共享属性添加接口实际上只有在 DefectViewModel 所在的库的上下文中才有意义。
          • 这是有道理的。听起来您被一种基于反射的解决方案所困扰。不过,我推荐 Henk 关于使用构造函数的建议。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2014-02-27
          • 1970-01-01
          • 2023-03-20
          • 1970-01-01
          • 1970-01-01
          • 2020-05-19
          • 1970-01-01
          相关资源
          最近更新 更多