【问题标题】:C# Find properties shared by two types of the same hierarchyC# 查找同一层次结构的两种类型共享的属性
【发布时间】:2018-12-28 21:45:00
【问题描述】:

我需要将属性的值从一个类复制到另一个是相同基类型的后代。源对象和目标对象可能位于同一继承分支的不同级别,即一个继承自另一个,或者是不同分支的后代,即它们共享一个共同的基类型。

      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);
    }
}

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

【问题讨论】:

  • 您尝试过使用 AutoMapper 吗?您可以将其配置为将一种类型的所有同名属性复制到另一种类型,而无需自己实现。
  • 我知道 AutoMapper,但只为这个用例添加依赖项似乎有点过头了。它不是单个应用程序,而是框架的一部分,因此添加依赖项需要更加谨慎。
  • @Toxantron 这可能不是矫枉过正,如果您必须动态执行此操作,如果您知道模型及其仅适用于少数或基本类型,您可以创建手动适配器,但如果它频繁或动态只用automapper,用反射写一个工具会比automapper慢。
  • 只是在 automapper 上加入 +1。它非常轻巧,被广泛使用和喜爱。

标签: c# optimization reflection


【解决方案1】:

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

您为希望能够从/复制到的每一对类型创建一个这些属性复制器对象的实例。复制器对象一经创建便不可变,因此它可以是长期存在的、静态的、可在多个线程中使用(创建后)等。

这是 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 类,以及那组通用约束。

【讨论】:

  • 编辑了我的帖子,添加了两种类型(源和目标)必须具有公共基类的可选限制(即添加了PropertyCopierCommonBase
  • 这不适用于需要具有相同属性但类型不同的嵌套类,并且它使用反射,所以它会很慢,OP 应该使用自动映射器。
  • 您认为 Automapper 是如何工作的?如果你看代码,构造函数中有很多反射。这相当慢。但是,一旦构造了复制器对象,它就可以永远存在(它是不可变的并且相当小)。一旦你有了一个,复制是通过调用每个属性的两个 MethodInfo 实例来完成的(一个用于 get,一个用于 set)。调用 MethodInfo 仅比调用 Delegate 稍慢(这比调用常规函数的 callvirt 稍慢)。思路是把所有的工作都放在构造函数中,让操作快速起来。
  • Automapper 使用城堡代理,它避免通过反射调用,它只构建一次地图。例如,它避免了这种情况:var val = prop.SourceReader.Invoke(source, null); 它还支持重用它的地图。我对这个解决方案没有真正的问题,更重要的是,我认为为小依赖重新创建轮子是没有意义的
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-19
  • 2022-10-18
相关资源
最近更新 更多