使用反射在两个对象之间复制属性
问题
有时,您的对象具有许多公共属性,但对于特定任务,您只需要其中的一小部分属性,例如使用该对象创建 CSV 文件。例如,查看以下类:
public class User
{
public string Username{get;set;}
public string Address{get;set;}
public string Name{get;set;}
public string Lastname{get;set;}
}
public class Person
{
public string Name{get;set;}
public string Lastname{get;set;}
}
现在,例如,我们已经有一个User对象,我们想要为另一个任务创建一个Person对象,并从 User 对象复制Name和Lastname属性。
如果我们有这样的用户对象:
var user = new User()
{
Username = "murat",
Address = "Some address string here",
Name = "murat",
Lastname = "aykanat"
};
我们能做什么:
var person = new Person()
{
Name = user.Name,
Lastname = user.Lastname
};
但是,如果我们在“父”对象上有 30 个属性,在“子”对象上有 15 个属性,那么我们必须编写 15 行重复、无聊且笨重的代码。一定还有其他方法!
解决方案
有两种方法可以避免这种困境:
- 我们可以使用反射将类似命名的属性从“父”对象复制到“子”对象。
- 我们可以使用“子”对象中的属性来“标记”它们,以便父对象使用反射将其值复制到“子”对象。
属性复制
让我们从简单的开始。我们将创建一个名为PropertyCopier的类,使用静态方法Copy将公共属性从父对象复制到子对象。为了简化我们的任务,我们将仅复制名称相似的属性。
在这个方法中,我们将有两个foreach循环迭代子对象和父对象的公共属性,不断检查它们的名称和类型是否匹配。每当我们找到匹配项时,我们就会将值复制到子对象的相应属性中。
public class PropertyCopier<TParent, TChild> where TParent : class
where TChild : class
{
public static void Copy(TParent parent, TChild child)
{
var parentProperties = parent.GetType().GetProperties();
var childProperties = child.GetType().GetProperties();
foreach (var parentProperty in parentProperties)
{
foreach (var childProperty in childProperties)
{
if (parentProperty.Name == childProperty.Name && parentProperty.PropertyType == childProperty.PropertyType)
{
childProperty.SetValue(child, parentProperty.GetValue(parent));
break;
}
}
}
}
}
我们可以在下面看到用法:
var user = new User()
{
Username = "murat",
Address = "Some address string here",
Name = "murat",
Lastname = "aykanat"
};
var person = new Person();
PropertyCopier<User, Person>.Copy(user, person);
Console.WriteLine("Person:");
Console.WriteLine(person.Name);
Console.WriteLine(person.Lastname);
输出将是:
Person:
murat
aykanat
物业匹配
当我们拥有类似的名称和类型时,一切都很好,但如果我们想要更动态的东西怎么办?我们经常有一些类,其中复制是有用的,但名称因类而异。此外,我们当前的复制器几乎无法控制哪些属性实际上被复制到子类。我们可能希望避免发送某些属性,即使它们的名称和类型匹配。
以下是我们当前版本的一些限制的示例:
public class PersonMatch
{
public string NameMatch { get; set; }
public string LastnameMatch { get; set; }
}
类型匹配,但名称不匹配。我们原来的PropertyCopier在这种情况下不起作用。
更好的方法
我们需要一种方法来“标记”我们想要从父对象复制的属性。最好的方法是使用属性来标记这些属性。
属性可以定义为元数据。它们可以分配给类、属性、方法等。它们提供设置它们的对象的元数据。例如,我们可以定义字符串属性的最大长度。当我们将字符串设置为属性时,它将首先检查我们事先提供的元数据,并根据新字符串值的长度将其设置为新值或不设置为新值。
在我们的例子中,我们将使用属性来定义父类属性名称。我们将告诉代码,这个子类属性将从父类实例中的属性中获取其值。
首先,我们需要创建一个类来定义我们的属性。它将是一个从Attribute类派生的简单类。它将有一个公共字段,用于定义父对象中的匹配属性名称。这是我们属性的代码:
AttributeUsage(AttributeTargets.Property)]
public class MatchParentAttribute : Attribute
{
public readonly string ParentPropertyName;
public MatchParentAttribute(string parentPropertyName)
{
ParentPropertyName = parentPropertyName;
}
}
定义了属性后,我们可以向我们之前的类添加必要的属性:
public class PersonMatch
{
[MatchParent("Name")]
public string NameMatch { get; set; }
[MatchParent("Lastname")]
public string LastnameMatch { get; set; }
}
现在我们要做的就是检查子对象中每个属性的属性值,并在父对象中找到匹配的名称:
public class PropertyMatcher<TParent, TChild> where TParent : class
where TChild : class
{
public static void GenerateMatchedObject(TParent parent, TChild child)
{
var childProperties = child.GetType().GetProperties();
foreach (var childProperty in childProperties)
{
var attributesForProperty = childProperty.GetCustomAttributes(typeof(MatchParentAttribute), true);
var isOfTypeMatchParentAttribute = false;
MatchParentAttribute currentAttribute = null;
foreach (var attribute in attributesForProperty)
{
if (attribute.GetType() == typeof(MatchParentAttribute))
{
isOfTypeMatchParentAttribute = true;
currentAttribute = (MatchParentAttribute) attribute;
break;
}
}
if (isOfTypeMatchParentAttribute)
{
var parentProperties = parent.GetType().GetProperties();
object parentPropertyValue = null;
foreach (var parentProperty in parentProperties)
{
if (parentProperty.Name == currentAttribute.ParentPropertyName)
{
if (parentProperty.PropertyType== childProperty.PropertyType)
{
parentPropertyValue = parentProperty.GetValue(parent);
}
}
}
childProperty.SetValue(child, parentPropertyValue);
}
}
}
}
让我们尝试一下我们的代码并看看我们得到了什么:
var user = new User()
{
Username = "murat",
Address = "Some address string here",
Name = "murat",
Lastname = "aykanat"
};
var personMatch = new PersonMatch();
PropertyMatcher<User, PersonMatch>.GenerateMatchedObject(user, personMatch);
Console.WriteLine("Person:");
Console.WriteLine(personMatch.NameMatch);
Console.WriteLine(personMatch.LastnameMatch);
输出将是:
Person:
murat
aykanat
扩展方法中的属性复制/匹配
如果我们不想使用PropertyCopier或PropertyMatcher类,我们可以为object类型定义扩展方法。我们将使用与上面相同的代码来填充方法:
public static class ObjectExtensionMethods
{
public static void CopyPropertiesFrom(this object self, object parent)
{
var fromProperties = parent.GetType().GetProperties();
var toProperties = self.GetType().GetProperties();
foreach (var fromProperty in fromProperties)
{
foreach (var toProperty in toProperties)
{
if (fromProperty.Name == toProperty.Name && fromProperty.PropertyType == toProperty.PropertyType)
{
toProperty.SetValue(self, fromProperty.GetValue(parent));
break;
}
}
}
}
public static void MatchPropertiesFrom(this object self, object parent)
{
var childProperties = self.GetType().GetProperties();
foreach (var childProperty in childProperties)
{
var attributesForProperty = childProperty.GetCustomAttributes(typeof(MatchParentAttribute), true);
var isOfTypeMatchParentAttribute = false;
MatchParentAttribute currentAttribute = null;
foreach (var attribute in attributesForProperty)
{
if (attribute.GetType() == typeof(MatchParentAttribute))
{
isOfTypeMatchParentAttribute = true;
currentAttribute = (MatchParentAttribute)attribute;
break;
}
}
if (isOfTypeMatchParentAttribute)
{
var parentProperties = parent.GetType().GetProperties();
object parentPropertyValue = null;
foreach (var parentProperty in parentProperties)
{
if (parentProperty.Name == currentAttribute.ParentPropertyName)
{
if (parentProperty.PropertyType== childProperty.PropertyType)
{
免责声明:本内容来源于第三方作者授权、网友推荐或互联网整理,旨在为广大用户提供学习与参考之用。所有文本和图片版权归原创网站或作者本人所有,其观点并不代表本站立场。如有任何版权侵犯或转载不当之情况,请与我们取得联系,我们将尽快进行相关处理与修改。感谢您的理解与支持!
请先 登录后发表评论 ~