RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 762539
Accepted
Kunoichi
Kunoichi
Asked:2020-12-24 09:15:09 +0000 UTC2020-12-24 09:15:09 +0000 UTC 2020-12-24 09:15:09 +0000 UTC

C#,如何通过为该对象创建表达式来将对象的字段复制到另一个对象

  • 772

我想编写一个函数,将相同的属性从一个对象复制到另一个对象,但同时它可以快速工作。这里有什么:

static class Copy
{
    private static readonly ConcurrentDictionary<Type, ConcurrentDictionary<string, PropertyInfo>> PropertiesDictionaries
        = new ConcurrentDictionary<Type, ConcurrentDictionary<string, PropertyInfo>>();

    public static void Сopyfields(object source, object target)
    {
        var sourceType = source.GetType();
        var targetType = target.GetType();
        var sourceProperties = GetProperties(sourceType);
        var targetPropertyes = GetProperties(targetType);

        foreach (var targetProperty in targetPropertyes)
        {
            if (sourceProperties.TryGetValue(targetProperty.Key, out var sourceProperty))
            {
                targetProperty.Value.SetValue(target, sourceProperty.GetValue(source));
            }
        }
    }

    private static ConcurrentDictionary<string, PropertyInfo> GetProperties(Type objType)
    {
        if (!PropertiesDictionaries.TryGetValue(objType, out var propertiesInfoDictionary))
        {
            var infos = objType.GetProperties();
            propertiesInfoDictionary = new ConcurrentDictionary<string, PropertyInfo>();
            foreach (var propertyInfo in infos)
            {
                propertiesInfoDictionary.GetOrAdd(propertyInfo.Name, propertyInfo);
            }
            PropertiesDictionaries.GetOrAdd(objType, propertiesInfoDictionary);
        }
        return propertiesInfoDictionary;
    }
}

如何重写此函数以创建表达式并为每种类型组合仅使用一次反射?

c#
  • 1 1 个回答
  • 10 Views

1 个回答

  • Voted
  1. Best Answer
    VladD
    2020-12-28T06:09:27Z2020-12-28T06:09:27Z

    这是最简单的选择:

    class MapperFactory
    {
        public static Action<P, Q> CreateMapper<P, Q>()
        {
            var sourceType = typeof(P);
            var targetType = typeof(Q);
    
            var sourceProperties = GetVisibleProperties(sourceType);
            var targetProperties = GetVisibleProperties(targetType);
    
            var fromVar = Expression.Variable(sourceType, "from");
            var toVar = Expression.Variable(targetType, "to");
    
            Expression CreateCopyProperty(string name)
            {
                try
                {
                    return Expression.Assign(
                        Expression.Property(toVar, targetProperties[name]),
                        Expression.Property(fromVar, sourceProperties[name]));
                }
                catch (Exception ex)
                {
                    throw new ArgumentException($"Невозможно скопировать свойство {name}", ex);
                }
            }
    
            var commonProperties = sourceProperties.Keys.Intersect(targetProperties.Keys)
                                                        .OrderBy(n => n);
            var assignExpressions = commonProperties.Select(CreateCopyProperty);
            var assignment = Expression.Block(assignExpressions);
            var lambda = Expression.Lambda<Action<P, Q>>(assignment, fromVar, toVar);
            return lambda.Compile();
        }
    
        static Dictionary<string, PropertyInfo> GetVisibleProperties(Type type)
        {
            IEnumerable<Type> GetTypeChain(Type t) =>
                t == null ? Enumerable.Empty<Type>() : GetTypeChain(t.BaseType).Prepend(t);
    
            Dictionary<string, PropertyInfo> result = new Dictionary<string, PropertyInfo>();
            foreach (var t in GetTypeChain(type))
            {
                var properties = t.GetProperties(BindingFlags.Instance |
                                            BindingFlags.Public | BindingFlags.DeclaredOnly);
                foreach (var prop in properties)
                {
                    if (!result.ContainsKey(prop.Name))
                        result.Add(prop.Name, prop);
                }
            }
            return result;
        }
    }
    

    它不检查可访问性,不查看私有/受保护,也不检查类型兼容性。如果无法复制同名属性,则在创建映射器时会抛出异常。如果目标属性不可写,则在创建映射器时会抛出异常。不匹配的属性会被静默忽略。如果派生类的属性在名称上覆盖了基类的属性,则忽略基类的属性(您还能做什么?)。静态属性被忽略(这似乎是正确的)。显式接口实现的属性将被忽略(因为我还没有弄清楚如何处理它们)。

    像这样使用:

    class From
    {
        public int A { get; set; }
        public string B { get; set; }
    }
    
    class To
    {
        public int A { get; set; }
        public string C { get; set; }
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            Action<From, To> map = MapperFactory.CreateMapper<From, To>();
            var from = new From() { A = 15, B = "hello world" };
            var to = new To();
            map(from, to);
            Console.WriteLine(to.A); // 15
        }
    }
    

    正常工作且不会崩溃的选项:

    interface I
    {
        int X { get; set; }
    }
    
    class FromBase
    {
        public string A { get; set; }
        public int B { get; set; }
    }
    
    class From : FromBase, I
    {
        public new int A { get; set; }
        static public string C { get; set; }
        int I.X { get; set; }
    }
    
    class To
    {
        public int A { get; set; }
        public string C { get; set; }
    }
    

    解释。

    我们先来看看函数GetVisibleProperties。我们需要获取所有属性的列表。这通常用一个简单的 来完成type.GetProperties(),但是如果我们有多个同名的属性呢?如果基类中的属性被覆盖,就会发生这种情况new,如上一个示例所示。如何只获得最新的财产?

    要做到这一点,让我们做这个技巧:让我们遍历从我们的类型到的基本类型链object,并且在每一步它将只添加在该类型中定义的属性,并且只添加那些名称尚未与我们相遇的属性. 为此,我们需要获得一系列基本类型。最简单的方法是递归:将我们自己的类型添加到基类的链顶部。结果很简单:

    IEnumerable<Type> GetTypeChain(Type t) =>
        t == null ? Enumerable.Empty<Type>() : GetTypeChain(t.BaseType).Prepend(t);
    

    BindingFlags.Public好的,那么我们遵循这个链,对于每种类型,我们只获得公共属性BindingFlags.Instance(BindingFlags.DeclaredOnly对于它们中的每一个,我们检查是否已经存在同名的属性,并且仅当还没有同名的属性时才将新属性添加到结果中。

    与GetVisibleProperties一切。现在,主要功能:CreateMapper.

    我们将类型作为泛型参数传递,以便我们可以返回一个类型化的委托Action<P, Q>。首先,我们使用已经解析的GetVisibleProperties. 接下来,我们取名称集的交集:这些是两种类型中的属性的名称 ( commonProperties)。我还按字母顺序对它们进行排序,以便顺序相同。

    我们通过System.Linq.Expressions,它允许您Expression在代码中构造表达式(如 )。这是一种标准的代码生成技术,我们在网站上有很多使用这种技术的答案。

    所以这里是辅助函数CreateCopyProperty。它创建了表达式的类似物to.Prop1 = from.Prop1;。我们预先定义了Expression-variablesfromVar并toVar对应于类型P和Q命名的“真实”变量from和to。Expression.Property(toVar, targetProperties[name])- 这是一个完整的模拟to.Prop1,其中属性是Prop1通过定义的PropertyInfo(这对于我们可能有重叠名称的情况是必要的)。Expression.Assign当然,创建分配的代码。例如,如果属性的类型不兼容,或者目标属性没有设置器,则创建表达式可能会引发异常。有可能不捕获此异常,但不清楚哪个属性是“应受责备”,所以我添加了一个 try / catch 块,并在新异常的文本中提到了有问题的属性的名称.

    好的,接下来。我们有一组属性名称 ( commonProperties),使用Select它来创建一组Expression's。这个集合需要打包成一个 using Expression.Block,它创建了一个花括号块的模拟:

    { to.Prop1 = from.Prop1; to.Prop2 = from.Prop2; ... }
    

    现在我们从这个块中创建一个 lambda:

    (from, to) => { to.Prop1 = from.Prop1; to.Prop2 = from.Prop2; ... }
    

    在 的帮助下Expression.Lambda。

    最后一个技巧是调用Compile,它Expression编译成一个真正的函数,我们返回给用户。

    • 9

相关问题

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    Python 3.6 - 安装 MySQL (Windows)

    • 1 个回答
  • Marko Smith

    C++ 编写程序“计算单个岛屿”。填充一个二维数组 12x12 0 和 1

    • 2 个回答
  • Marko Smith

    返回指针的函数

    • 1 个回答
  • Marko Smith

    我使用 django 管理面板添加图像,但它没有显示

    • 1 个回答
  • Marko Smith

    这些条目是什么意思,它们的完整等效项是什么样的

    • 2 个回答
  • Marko Smith

    浏览器仍然缓存文件数据

    • 1 个回答
  • Marko Smith

    在 Excel VBA 中激活工作表的问题

    • 3 个回答
  • Marko Smith

    为什么内置类型中包含复数而小数不包含?

    • 2 个回答
  • Marko Smith

    获得唯一途径

    • 3 个回答
  • Marko Smith

    告诉我一个像幻灯片一样创建滚动的库

    • 1 个回答
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Алексей Шиманский 如何以及通过什么方式来查找 Javascript 代码中的错误? 2020-08-03 00:21:37 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    user207618 Codegolf——组合选择算法的实现 2020-10-23 18:46:29 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5