吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 233|回复: 25
收起左侧

[经验求助] C# 深层拷贝,多层,快速

[复制链接]
夜雨闻笛 发表于 2024-5-24 17:31
50吾爱币
本帖最后由 夜雨闻笛 于 2024-5-24 17:34 编辑

大佬好,我的需求如下:
需要复制一个类,类里有引用类型字段,字段里可能还存在嵌套的引用类型。
想要深度复制,复制后各字段值相同,但互不相干,改变一个类引用类型的值后,不会影响被复制者的对应值。


自己解决需求时遇到的问题如下:
1:最开始用MemberwiseClone写了一大堆觉得真好用,等测试的时候才发现这是浅复制,导致bug一大堆。
2.1:然后去网上复制代码多套,但都有各自的问题。第一套:
T tOut = Activator.CreateInstance<T>();
Type tInType = this.GetType();
PropertyInfo[] array = tOut.GetType().GetProperties();
for (int i = 0; i < array.Length; i++)
{
    PropertyInfo? itemOut = array;
    var itemIn = tInType.GetProperty(itemOut.Name); ;
    if (itemIn != null)
    {
        itemOut.SetValue(tOut, itemIn.GetValue(this));
    }
}
return tOut;

这一段代码的速度是令人满意的,10万次在我电脑上大约1.6秒,可以接受。但复制结果不对,遇到list他不复制,而是直接给我new一个新的空list出来,这导致list里的数据会全部为空。


2.2:第二套:
T model = Activator.CreateInstance<T>();                     //实例化一个T类型对象
PropertyInfo[] propertyInfos = model.GetType().GetProperties();     //获取T对象的所有公共属性
foreach (PropertyInfo propertyInfo in propertyInfos)
{
    //判断值是否为空,如果空赋值为null见else
    if (propertyInfo.PropertyType.IsGenericType && propertyInfo.PropertyType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
    {
        //如果convertsionType为nullable类,声明一个NullableConverter类,该类提供从Nullable类到基础基元类型的转换
        NullableConverter nullableConverter = new(propertyInfo.PropertyType);
        //将convertsionType转换为nullable对的基础基元类型
        propertyInfo.SetValue(model, Convert.ChangeType(propertyInfo.GetValue(this), nullableConverter.UnderlyingType), null);
    }
    else
    {
        propertyInfo.SetValue(model, Convert.ChangeType(propertyInfo.GetValue(this), propertyInfo.PropertyType), null);
    }
}
return model;

问题和第一套一样,效率一致但还只是半浅半深的复制。


2.3:第三套  json序列化,这一套解决了复制不够深的问题,但效率略低,跑10万次花了接近19秒(如果本帖求不到更好的解决方法,我也只能用这个方法凑合用了)
string 序列化内容 = JsonConvert.SerializeObject(this);
T? 新 = JsonConvert.DeserializeObject<T>(序列化内容);

return 新;


2.4:第四套   我看不懂但跑了一下,效率低的离谱,10万次花了60秒!(可是某乎上说,AutoMapper方法运行很快,一百万次才300多毫秒??是我复制的代码写的不好还是什么问题?怎么效率差这么多?
var config = new MapperConfiguration(cfg =>
{
    // 针对每一层嵌套的类型进行映射配置
    cfg.CreateMap<T, T>();
    cfg.CreateMap<T, T>();
});
// 创建映射器
IMapper mapper = config.CreateMapper();
// 使用映射器进行深度复制
T 新 = mapper.Map<T>(this);
return 新;


2.5:还有有大佬用  BinaryFormatter  这种方法写,说是挺好用的,但我也测试不了,因为这个已经被弃用了,代码无法通过检查。我也找不到他的平替品。



最后又看到有人说手写复制最快,一行行代码挨个赋值,遇到引用类型再去引用类型里写手写复制……
有点难绷,要是真手写我可能要多写上千行,而且后面万一新增点什么东西,还容易错写漏写。


问题总结:
深度多层复制一个类的方法,要求效率比json序列化快5倍以上即可。
我电脑用json方法跑10万次耗时19秒,不求像浅复制那样10万次跑进1秒之内的方法,好歹能有一个10万次跑4秒以内的方法吧?否则太慢真让人不舒服。
★希望大佬给出的是完整可运行代码,而不是思路。因为大佬们看我的描述也知道我是个圈外人,不是程序员,写的东西都是很浅显的,没有经过系统的学习,您们给我说个思路我真没法玩……
跪谢!


最佳答案

查看完整内容

推荐使用 ,一个开源的深拷贝的类库, 不用自己实现了, 安全高效,测试了一下简单的10万次, 也能在1秒之内 https://github.com/force-net/DeepCloner Install-Package DeepCloner Usage Deep cloning any object: var clone = new { Id = 1, Name = "222" }.DeepClone();

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

Broadm 发表于 2024-5-24 17:31
推荐使用 ,一个开源的深拷贝的类库, 不用自己实现了, 安全高效,测试了一下简单的10万次, 也能在1秒之内
https://github.com/force-net/DeepCloner

        Install-Package DeepCloner
Usage
Deep cloning any object:

  var clone = new { Id = 1, Name = "222" }.DeepClone();
cndeng 发表于 2024-5-24 17:41
反射方法实现深度复制
[C#] 纯文本查看 复制代码

using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;

public static class DeepCloner
{
    public static T DeepClone<T>(this T source)
    {
        if (ReferenceEquals(source, null)) 
            return default;

        return (T)DeepClone((object)source);
    }

    private static object DeepClone(object source)
    {
        if (source == null)
            return null;

        Type type = source.GetType();

        if (type.IsPrimitive || type == typeof(string))
        {
            return source;
        }
        else if (type.IsArray)
        {
            Type elementType = type.GetElementType();
            var array = source as Array;
            var copied = Array.CreateInstance(elementType, array.Length);
            for (int i = 0; i < array.Length; i++)
            {
                copied.SetValue(DeepClone(array.GetValue(i)), i);
            }
            return copied;
        }
        else if (typeof(IList).IsAssignableFrom(type))
        {
            Type itemType = type.GetGenericArguments()[0];
            var collection = Activator.CreateInstance(type) as IList;
            var list = source as IList;
            foreach (var item in list)
            {
                collection.Add(DeepClone(item));
            }
            return collection;
        }
        else
        {
            var clone = Activator.CreateInstance(type);
            PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
            foreach (var property in properties)
            {
                if (property.CanRead && property.CanWrite)
                {
                    var value = property.GetValue(source);
                    property.SetValue(clone, DeepClone(value));
                }
            }
            return clone;
        }
    }
}

public class TestClass
{
    public string Name { get; set; }
    public List<int> Numbers { get; set; }
    public NestedClass Nested { get; set; }
}

public class NestedClass
{
    public int Id { get; set; }
    public string Description { get; set; }
}

// Example usage
public class Program
{
    public static void Main(string[] args)
    {
        var original = new TestClass
        {
            Name = "Original",
            Numbers = new List<int> { 1, 2, 3 },
            Nested = new NestedClass { Id = 1, Description = "Nested" }
        };

        var clone = original.DeepClone();

        // Test the clone
        clone.Name = "Clone";
        clone.Numbers[0] = 42;
        clone.Nested.Id = 2;

        Console.WriteLine(original.Name); // Output: Original
        Console.WriteLine(original.Numbers[0]); // Output: 1
        Console.WriteLine(original.Nested.Id); // Output: 1
    }
}

 楼主| 夜雨闻笛 发表于 2024-5-24 19:36
cndeng 发表于 2024-5-24 17:41
反射方法实现深度复制
[mw_shl_code=csharp,true]

cndeng大佬你好,首先感蟹你的回复。
你的方法我试用了,出现的问题是:如果这个类的引用类型是字段而不是属性,它就无法复制。但如果把字段改成属性搞上get set外套,则可以复制成功。
如:把TestClass类中的属性  public List<int> Numbers { get; set; }  改成字段 public List<int> Numbers = [];   则无法复制此字段中的值。
现在的问题是我的代码已经写了很多了,如果要把一个类的所有字段都改成属性,许多地方都要有相应的修改,工程量有点大。
不知道大佬有没有办法优化一下这个方法?让他可以兼顾字段引用类型的复制。
cndeng 发表于 2024-5-24 21:31
没办法 , 长痛不如短痛 , 还是规范代码吧
NEmo11 发表于 2024-5-25 00:26
什么叫深度复制一个类? 你想说的是深度复制一个类对象吧?

试试System.Text.Json中的JsonSerializer
这个性能比较好。
其次,要追求效率的话,比如你要深度辅助多个类对象,多线程去操作就好了,一样可以拉高效率
flyer_2001 发表于 2024-5-25 00:34
夜雨闻笛 发表于 2024-5-24 19:36
cndeng大佬你好,首先感蟹你的回复。
你的方法我试用了,出现的问题是:如果这个类的引用类型是字段而不 ...

public List<int> Numbers = new List<int>{1,2,3,4};
这种是可以复制的

public List<int> Numbers;
然后在构造函数或者其它地方赋值的,可以复制
 楼主| 夜雨闻笛 发表于 2024-5-25 01:12
NEmo11 发表于 2024-5-25 00:26
什么叫深度复制一个类? 你想说的是深度复制一个类对象吧?

试试System.Text.Json中的JsonSerializer

多线程我还没学到,哈哈,等我学到了再研究多线程怎么写。
刚开始看教程的时候倒是见到过多线程,但是听说还要加锁什么的,心里害怕,想着这种高端的东西不是我这样的门外人能窥探的,就一直都没研究过。
 楼主| 夜雨闻笛 发表于 2024-5-25 01:18
cndeng 发表于 2024-5-24 21:31
没办法 , 长痛不如短痛 , 还是规范代码吧

大佬你好,熬夜搞了一波,终于把所有字段都改完了,现在全部是属性了。
但克隆到一半,还是弹出了:
System.ArgumentException:“Object of type 'System.Object' cannot be converted to type
这个报错。
这个报错不应该是字段无法转成对应类型的报错吗?可是我已经把所有字段都修改成属性了啊。
我在百度搜不到解决办法,只能再来问问大佬。
 楼主| 夜雨闻笛 发表于 2024-5-25 01:23
cndeng 发表于 2024-5-24 21:31
没办法 , 长痛不如短痛 , 还是规范代码吧

我试了一下,好像把那些报错的类型,从可空改成不可空就不再报错了。
比如 float[]? 就报错,可float[]就不报错。
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-6-17 13:49

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表