[英]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). 我有两个C#类,它们具有许多相同的属性(按名称和类型)。 I want to be able to copy all non-null values from an instance of
Defect
into an instance of DefectViewModel
. 我希望能够将
Defect
实例中的所有非空值复制到DefectViewModel
的实例中。 I was hoping to do it with reflection, using GetType().GetProperties()
. 我希望用反射来做,使用
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)
? 我应该维护单独的
Defect
属性列表和DefectViewModel
属性,以便我可以执行viewModelProperty.SetValue(viewModel, defectValue, null)
吗?
Edit: thanks to both Jordão's and Dave's answers, I chose AutoMapper. 编辑:多亏了Jordão和Dave的答案,我选择了AutoMapper。
DefectViewModel
is in a WPF application, so I added the following App
constructor: 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
)
);
}
Then, instead of all that PropertyInfo
business, I just have the following line: 然后,我只是拥有以下行,而不是所有
PropertyInfo
业务:
var defect = new Defect();
var defectViewModel = new DefectViewModel();
Mapper.Map<Defect, DefectViewModel>(defect, defectViewModel);
看看AutoMapper 。
There are frameworks for this, the one I know of is Automapper: 有这个框架,我所知道的是Automapper:
http://automapper.codeplex.com/ http://automapper.codeplex.com/
http://www.lostechies.com/blogs/jimmy_bogard/archive/2009/01/22/automapper-the-object-object-mapper.aspx http://www.lostechies.com/blogs/jimmy_bogard/archive/2009/01/22/automapper-the-object-object-mapper.aspx
This is cheap and easy. 这很便宜而且容易。 It makes use of System.Web.Script.Serialization and some extention methods for ease of use:
它使用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>();
}
}
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
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. 您发布的代码正在尝试在
DefectViewModel
对象上设置Defect
-tied属性。
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: 在组织代码方面,如果你不想要像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)));
}
}
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.
您只需要在其接口列表中引用mixins。
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. 请注意,这不像AutoMapper那样强大或灵活,因为您可能希望复制具有不同名称的属性或仅属性的某些子集。 Or it might downright fail if the properties don't provide the necessary getters or setters or their types differ.
如果属性不提供必要的getter或setter或它们的类型不同,它可能会彻底失败。 But, it still might be enough for your purposes.
但是,它仍然可能足以满足您的目的。
For one thing I would not place that code (somewhere) external but in the constructor of the ViewModel: 首先,我不会将该代码(某处)放在外部但是在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.
自动化它看起来不错,但可能会有比您预期的更多异常和特殊情况。
你有可能让两个类都实现一个定义共享属性的接口吗?
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.