C#查找同一层次结构的两种类型共享的属性

时间:2018-07-20 14:35:01

标签: c# optimization reflection

我需要将属性的值从一个类复制到另一个具有相同基本类型的后代。源对象和目标对象可能在同一继承分支的不同级别上,这意味着一个继承自另一个,或者是不同分支的后代,这意味着它们具有相同的基本类型。

      A
  B1      B2
C1 C2      C3

从上面的结构中,我可能想要将所有属性从A复制到C1,将C2复制到C3,将C3复制到B1等。基本上,从树中复制任何可能的组合。显然,我只能复制源类型中存在的属性,也必须存在于目标类型中。

迭代源类型的属性很容易

var sourceProperties = source.GetType().GetProperties();

但是,如何检查在目标类型上声明了哪个属性?仅按名称检查是不够的,因为它们可能具有不同的类型。同样,在过去,我使用new在重复属性方面也有糟糕的经历。

不幸的是,C#或.NET没有内置方法来检查类型是否具有诸如PropertyInfo之类的特定Type.HasProperty(PropertyInfo)。我能想到的最好的办法是检查该属性是否由共享基类型声明。

public static void CopyProperties(object source, object target)
{
    var targetType = target.GetType();
    var sharedProperties =source.GetType().GetProperties()
        .Where(p => p.DeclaringType.IsAssignableFrom(targetType));

    foreach (var property in sharedProperties)
    {
        var value = property.GetValue(source);
        if (property.CanWrite)
            property.SetValue(target, value);
    }
}

问题:有更好的解决方案吗?

1 个答案:

答案 0 :(得分:0)

这是不需要继承的解决方案。只要名称和类型匹配,它将属性从一个对象(一种类型)复制到另一个对象(另一种类型)。

您要为要复制的每对类型创建一个属性复制器对象实例。复印机对象一旦创建便是不可变的,因此它可以是长期的,静态的,可以在许多线程(创建后)中使用,等等。

这是PropertyCopier类的代码。创建此类型的对象时,需要指定源和目标类型。

public class PropertyCopier<TSource, TDest> where TSource : class where TDest : class
{
    private List<PropertyCopyPair> _propertiesToCopy = new List<PropertyCopyPair>();

    public PropertyCopier()
    {
        //get all the readable properties of the source type
        var sourceProps = new Dictionary<string, Tuple<Type, MethodInfo>>();
        foreach (var prop in typeof(TSource).GetProperties(BindingFlags.Instance | BindingFlags.Public))
        {
            if (prop.CanRead)
            {
                sourceProps.Add(prop.Name, new Tuple<Type, MethodInfo>(prop.PropertyType, prop.GetGetMethod()));
            }
        }

        //now walk though the writeable properties of the destination type
        //if there's a match by name and type, keep track of them.

        foreach (var prop in typeof(TDest).GetProperties(BindingFlags.Instance | BindingFlags.Public))
        {
            if (prop.CanWrite)
            {
                if (sourceProps.ContainsKey(prop.Name) && sourceProps[prop.Name].Item1 == prop.PropertyType)
                {
                    _propertiesToCopy.Add (new PropertyCopyPair(prop.Name, prop.PropertyType, sourceProps[prop.Name].Item2, prop.GetSetMethod()));
                }
            }
        }
    }

    public void Copy(TSource source, TDest dest)
    {
        foreach (var prop in _propertiesToCopy)
        {
            var val = prop.SourceReader.Invoke(source, null);
            prop.DestWriter.Invoke(dest, new []{val});
        }
    }
}

它依赖于看起来像这样的帮助程序类(可以简化该类;可以在其中进行调试的其他属性(可能对您有用))。

public class PropertyCopyPair
{
    public PropertyCopyPair(string name, Type theType, MethodInfo sourceReader, MethodInfo destWriter)
    {
        PropertyName = name;
        TheType = theType;
        SourceReader = sourceReader;
        DestWriter = destWriter;
    }

    public string PropertyName { get; set; }
    public Type TheType { get; set; }
    public MethodInfo SourceReader { get; set; }
    public MethodInfo DestWriter { get; set; }
}

我还创建了另一个真正的简单类进行测试:

public class TestClass
{
    public string PropertyName { get; set; }
    public Type TheType { get; set; }
    public string Other { get; set; }
}

所有这些都准备就绪,此代码将练习复印机类:

 var copier = new PropertyCopier<PropertyCopyPair, TestClass>();
 var source = new PropertyCopyPair("bob", typeof(string), null, null);
 var dest = new TestClass {Other = "other", PropertyName = "PropertyName", TheType = this.GetType()};
 copier.Copy(source, dest);

运行它时,将复制目标中具有相同名称和类型的属性的源的所有属性。

如果要将源类型和目标类型限制为通用基类,则可以执行以下操作:

public class PropertyCopierCommonBase<TSource, TDest, TBase> : PropertyCopier<TSource, TBase>
    where TBase : class where TSource : class, TBase where TDest : class, TBase
{  }

如果您不希望使用两个类,只需声明具有上述三个类型参数以及该类通用约束的原始PropertyCopier类即可。