杭州电子商务研究院

使用反射在两个对象之间复制属性

2017-02-13 08:00飞浪

问题

有时,您的对象具有许多公共属性,但对于特定任务,您只需要其中的一小部分属性,例如使用该对象创建 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)
                        {
                     
曝光33159浏览1709