C#中4种深拷贝方法介绍

C#中4种深拷贝方法介绍

在C#中,对象的拷贝通常分为浅拷贝和深拷贝。浅拷贝只是简单地复制变量值,两个对象所引用的堆内存空间是相同的;深拷贝则是创建一个新的对象,并复制其中所有的属性,两个对象所引用的堆内存空间是不同的。深拷贝通常在需要复制对象并修改其属性的情况下使用,而浅拷贝则更适合在对对象的只读访问上使用。

下面介绍C#中4种常用的深拷贝方法。

1. 使用序列化

使用序列化方法,可以将对象以字节的形式进行转换并复制。首先创建一个MemoryStream,然后通过创建BinaryFormatter对象并将对象序列化到MemoryStream中,最后将MemoryStream中的字节数组反序列化为新的对象即可。

public static T DeepCopy<T>(T obj) {
    using (var stream = new MemoryStream()) {
        var formatter = new BinaryFormatter();
        formatter.Serialize(stream, obj);
        stream.Seek(0, SeekOrigin.Begin);
        return (T)formatter.Deserialize(stream);
    }
}

示例

我们定义一个Person类,其中包含一个String类型的属性Name和一个对象类型的属性Attribute。

[Serializable]
class Person
{
    public string Name { get; set; }
    public object Attribute { get; set; }
}

我们创建了一个Person的实例对象p1,并为其赋值Name为"张三",Attribute为一个int类型的值100。然后将p1拷贝到p2中,并修改p2的Name为"李四",输出p1和p2的Name和Attribute值,可以看到两个对象的Name值不同,而Attribute的值相同,说明使用序列化方法成功地进行了拷贝。

Person p1 = new Person {Name = "张三", Attribute = 100};
Person p2 = DeepCopy(p1);
p2.Name = "李四";
Console.WriteLine(p1.Name + " " + p1.Attribute);
Console.WriteLine(p2.Name + " " + p2.Attribute);

输出结果为:

张三 100
李四 100

2. 使用反射

使用反射方法,可以遍历对象中的所有字段及其属性,并将其复制到一个新的对象中。需要注意的是,使用反射方法需要对字段的属性进行判断,避免出现代码死循环或者拷贝的信息不完整等问题。

public static T DeepCopy<T>(T obj)
{
    if (obj == null) return default(T);
    Type type = obj.GetType();
    if (type.IsValueType) return obj;
    object result = Activator.CreateInstance(type);
    FieldInfo[] fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
    foreach (FieldInfo field in fields)
    {
        try
        {
            object fieldValue = field.GetValue(obj);
            if (fieldValue != null)
            {
                if (!field.FieldType.IsValueType && field.FieldType != typeof(string))
                {
                    object deepCopy = DeepCopy(fieldValue);
                    field.SetValue(result, deepCopy);
                }
                else
                {
                    field.SetValue(result, fieldValue);
                }
            }
        }
        catch
        {
            continue;
        }
    }
    return (T)result;
}

示例

我们定义一个Person类,其中包含一个String类型的属性Name和一个Person类型的属性Father。

class Person
{
    public string Name { get; set; }
    public Person Father { get; set; }
}

我们创建了一个Person的实例对象p1,为其赋值Name为"张三",Father为一个新的Person实例对象p2,为p2的Name赋值为"李四"。然后将p1拷贝到p3中,并修改p3的Name为"王五",Father的Name为"赵六",输出p1、p2、p3三个对象的Name和Father的Name属性值,可以看到三个对象的Name值和Father的Name值均不同,说明使用反射方法成功地进行了拷贝。

Person p1 = new Person {Name = "张三", Father = new Person {Name = "李四"}};
Person p2 = p1.Father;
Person p3 = DeepCopy(p1);
p3.Name = "王五";
p3.Father.Name = "赵六";
Console.WriteLine(p1.Name + " " + p1.Father.Name);
Console.WriteLine(p2.Name + " " + p2.Father.Name);
Console.WriteLine(p3.Name + " " + p3.Father.Name);

输出结果为:

张三 李四
李四 李四
王五 赵六

3. 使用泛型枚举

使用泛型枚举方法,可以对所有支持枚举类型的对象进行拷贝。通过遍历枚举类型,将每一个枚举元素赋值到新的对象中即可。

public static T DeepCopy<T>(T obj) where T : class
{
    if (obj == null) return null;
    Type type = obj.GetType();
    if (type.IsValueType || type == typeof(string)) return obj;

    object result = Activator.CreateInstance(type);
    PropertyInfo[] properties = type.GetProperties();
    foreach (PropertyInfo property in properties)
    {
        if (property.CanWrite)
        {
            Type propertyType = property.PropertyType;
            if (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition().Equals(typeof(List<>)))
            {
                object value = property.GetValue(obj, null);
                Type[] genericArguments = property.PropertyType.GetGenericArguments();
                Type listItemType = genericArguments[0];
                Type listType = typeof(List<>).MakeGenericType(listItemType);
                object list = Activator.CreateInstance(listType);

                foreach (object listItem in (IEnumerable)value)
                {
                    object listItemCopy = DeepCopy(listItem);
                    listType.GetMethod("Add").Invoke(list, new[] { listItemCopy });
                }
                property.SetValue(result, list, null);
            }
            else if (propertyType.IsClass && propertyType != typeof(string))
            {
                object value = property.GetValue(obj, null);
                object valueCopy = DeepCopy(value);
                property.SetValue(result, valueCopy, null);
            }
            else
            {
                object value = property.GetValue(obj, null);
                property.SetValue(result, value, null);
            }
        }
    }
    return (T)result;
}

示例

我们定义一个Computer类,其中包含一个string类型的属性Name和一个List类型的属性Prices。

class Computer
{
    public string Name { get; set; }
    public List<double> Prices { get; set; }
}

我们创建了一个Computer的实例对象c1,为其赋值Name为"联想电脑",Prices为{10000.0, 8000.0},然后将c1拷贝到c2中,并修改c2的Name为"华硕电脑",Prices为{6000.0, 4000.0},输出c1和c2的Name和Prices属性值,可以看到两个对象的Name和Prices值分别不同,说明使用泛型枚举方法成功地进行了拷贝。

Computer c1 = new Computer {Name = "联想电脑", Prices = new List<double> {10000.0, 8000.0}};
Computer c2 = DeepCopy(c1);
c2.Name = "华硕电脑";
c2.Prices[0] = 6000.0;
c2.Prices[1] = 4000.0;
Console.WriteLine(c1.Name + " " + string.Join(",", c1.Prices));
Console.WriteLine(c2.Name + " " + string.Join(",", c2.Prices));

输出结果为:

联想电脑 10000,8000
华硕电脑 6000,4000

4. 使用Expression

使用Expression方法可以创建一个新的表达式树,并通过编译新的表达式树来生成一个新的对象。需要注意的是,使用Expression方法只支持对公共属性的拷贝。

public static Func<T, T> DeepCopy<T>()
{
    Type type = typeof(T);
    ParameterExpression parameter = Expression.Parameter(type, "p");
    List<MemberBinding> bindings = new List<MemberBinding>();
    foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
    {
        if (!property.CanRead || !property.CanWrite) continue;

        MemberExpression propertyAccess = Expression.Property(parameter, property);
        bindings.Add(Expression.Bind(property, propertyAccess));
    }
    Expression memberInit = Expression.MemberInit(Expression.New(type), bindings);
    Expression<Func<T, T>> lambda = Expression.Lambda<Func<T, T>>(memberInit, parameter);
    return lambda.Compile();
}

示例

我们定义一个Student类,其中包含一个string类型的属性Name和一个int类型的属性Age。

class Student
{
    public string Name { get; set; }
    public int Age { get; set; }
}

我们创建了一个Student的实例对象s1,为其赋值Name为"张三",Age为20,然后将s1拷贝到s2中,并修改s2的Name为"李四",Age为22,输出s1和s2的Name和Age属性值,可以看到两个对象的Name和Age值均不同,说明使用Expression方法成功地进行了拷贝。

Student s1 = new Student {Name = "张三", Age = 20};
Func<Student, Student> deepCopyFunc = DeepCopy<Student>();
Student s2 = deepCopyFunc(s1);
s2.Name = "李四";
s2.Age = 22;
Console.WriteLine(s1.Name + " " + s1.Age);
Console.WriteLine(s2.Name + " " + s2.Age);

输出结果为:

张三 20
李四 22

总结

以上是C#中4种主要的深拷贝方法,分别是使用:

  • 序列化方法
  • 反射方法
  • 泛型枚举方法
  • Expression方法

每种方法都有自己的优缺点,在选择时需要考虑到是否需要复制私有字段、是否需要兼容所有类型、是否需要能够进行延迟编译等问题。在实际应用中,可以根据需求综合考虑,选择合适的方法进行深拷贝。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:C#中4种深拷贝方法介绍 - Python技术站

(0)
上一篇 2023年5月31日
下一篇 2023年5月31日

相关文章

  • C#中序列化实现深拷贝,实现DataGridView初始化刷新的方法

    下面是关于“C#中序列化实现深拷贝,实现DataGridView初始化刷新的方法”的完整攻略,包含两个示例。 1. C#中序列化实现深拷贝 在C#中,可以使用序列化实现深拷贝。以下是一个示例: public static T DeepCopy<T>(T obj) { using (MemoryStream stream = new MemoryS…

    C# 2023年5月15日
    00
  • 记一次 .NET 某车零件MES系统 登录异常分析

    一:背景 1. 讲故事 这个案例有点特殊,以前dump分析都是和软件工程师打交道,这次和非业内人士交流,隔行如隔山,从指导dump怎么抓到问题解决,需要一个强大的耐心。 前几天有位朋友在微信上找到我,说他们公司采购的MES系统登录的时候出现了异常,让我帮忙看一下,我在想解铃还须系铃人,怎么的也不应该找到我呀,据朋友反馈项目已经验收,那边给了回馈是网络的问题,…

    C# 2023年5月8日
    00
  • C++泛型编程Generic Programming的使用

    C++泛型编程Generic Programming的使用攻略 什么是泛型编程Generic Programming 泛型编程是一种以通用算法为基础写程序的方式,它在写程序时把算法和数据结构的实现分开,以达到复用代码的目的。C++中泛型编程主要通过模板来实现。 泛型编程的优点 可重用性:泛型编程可以复用代码,使用一个函数解决多个问题。 可扩展性:当在实现具体…

    C# 2023年6月7日
    00
  • 带你一文了解C#中的Expression

    带你一文了解C#中的Expression 什么是Expression 在C#中,Expression是一个抽象类,它代表了一个包含单个值、操作符、变量、方法调用或属性访问等逻辑的树形结构。 Expression对象可以被应用于以程序方式表示代码逻辑的情况,通常被用于了解程序上下文、编译代码或构建API。具体来说,Expression很常用于Lambda表达式…

    C# 2023年6月1日
    00
  • asp.net core为IHttpClientFactory添加动态命名配置

    ASP.NET Core为IHttpClientFactory添加动态命名配置攻略 在ASP.NET Core中,我们可以使用IHttpClientFactory来创建和管理HttpClient实例。在某些情况下,我们需要为不同的HttpClient实例提供不同的配置。本攻略将介绍如何为IHttpClientFactory添加动态命名配置,并提供两个示例说明…

    C# 2023年5月17日
    00
  • 基于C#实现端口扫描器(单线程和多线程)

    基于C#实现端口扫描器(单线程和多线程) 端口扫描器是渗透测试和网络安全领域中一个非常重要的工具,它用于发现网络主机上开放的TCP/UDP端口。本文将基于C#实现一个简单的端口扫描器并探讨如何使用单线程和多线程技术来提高效率。 端口扫描器实现流程 解析待扫描主机的IP地址和端口范围 循环遍历端口范围,尝试向目标主机的每个端口发送TCP或UDP连接请求 根据返…

    C# 2023年5月15日
    00
  • C#实现的JS操作类实例

    C#实现的JS操作类实例是一种将JavaScript的函数和对象绑定到C#代码中的技术。它允许C#开发人员使用JavaScript函数和对象,从而可以轻松地利用JavaScript所提供的功能。以下是一个详细的攻略,帮助您了解如何使用C#实现JS操作类实例。 创建一个C#实现的JS操作类 首先,我们需要创建一个C#实现的JS操作类,以便在C#代码中使用Jav…

    C# 2023年6月7日
    00
  • C#用dynamic一行代码实现反射操作

    dynamic简介 dynamic是.NET Framework4.0的新特性。dynamic的出现让C#具有了弱语言类型的特性。编译器在编译的时候不再对类型进行检查,编译时默认dynamic对象支持你想要的任何特性。 dynamic简化反射实现 使用dynamic来简化反射实现是一种比较常见的编程技巧,它可以减少代码的复杂性并提高可读性。下面是一个使用dy…

    C# 2023年4月25日
    00
合作推广
合作推广
分享本页
返回顶部