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

yizhihongxing

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#取消令牌CancellationTokenSource

    针对你所提出的问题,我会给出逐步的解释和示例演示,来详细地讲解如何运用C#的取消令牌CancellationTokenSource。 什么是CancellationTokenSource? CancellationTokenSource是一个用于协作取消多个任务的机制。它提供了一种向多个任务同时发出取消信号的方法。 在使用CancellationTokenS…

    C# 2023年5月15日
    00
  • c# asp .net 动态创建sql数据库表的方法

    一、创建 SQL 数据库 打开 SQL Server Management Studio,连接到相应的服务器。 在 Object Explorer 窗口中,右键点击 Databases,选择 New Database… 新建一个数据库。 在弹出的对话框中输入选项: 输入数据库的名称,比如 TestDB。 指定数据库文件存储的路径。 选择数据文件的大小,以…

    C# 2023年5月31日
    00
  • asp.net 产生随机颜色实现代码

    产生随机颜色是 Web 开发中的一个比较常见的需求,下面是一份 asp.net 实现随机颜色的攻略。 方案一:使用 Random 类生成随机颜色值 可以通过 Random 类的 Next() 方法产生一个 32 位整数值,然后使用 Color.FromArgb() 方法将 32 位整数值转换为 Color 对象,从而实现随机颜色的生成。示例代码如下: Ran…

    C# 2023年5月31日
    00
  • C#中Lambda表达式的用法

    下面我来为你详细讲解“C#中Lambda表达式的用法”的完整攻略。 什么是Lambda表达式? Lambda表达式是一种语法糖(语言特性),它可以让你快速地定义一个匿名函数。Lambda表达式可以帮助我们消除冗余的代码、提高代码的可读性和可维护性。 语法格式如下: (parameters) => expression 这里parameters是形参列表…

    C# 2023年6月7日
    00
  • c#动态类型,及动态对象的创建,合并2个对象,map实例

    下面我将为您详细讲解C#动态类型、动态对象的创建、合并2个对象和Map实例的完整攻略。 C#动态类型 在C#中,我们可以使用dynamic关键字定义动态类型。动态类型在编译时不会进行类型检查,而是在运行时才确定类型。这样可以方便地处理一些不确定类型、或者类型不一致的情况,同时也可以增强代码的灵活性。 以下是一个动态类型的示例: dynamic dynamic…

    C# 2023年5月31日
    00
  • OData WebAPI实践-OData与EDM

    本文属于 OData 系列 引言 在 OData 中,EDM(Entity Data Model) 代表“实体数据模型”,它是一种用于表示 Web API 中的结构化数据的格式。EDM 定义了可以由 OData 服务公开的数据类型、实体和关系。 EDM 也提供了一些规则来描述数据模型中的实体之间的关系,例如继承、关联和复合类型。EDM 是 OData 协议的…

    C# 2023年5月11日
    00
  • 实例代码讲解c# 线程(上)

    让我来详细讲解一下“实例代码讲解c# 线程(上)”的完整攻略。 标题 首先,我们需要为文章设置标题。根据内容来判断,可以设置成如下格式: 实例代码讲解c# 线程(上) 介绍 在本篇文章中,我们将会介绍c#编程语言中线程的概念和使用方法。 线程是什么? 线程是程序执行的一条路径。在c#中,线程是一个轻量级的操作系统对象,它能够并发地执行代码。c#中的线程可以与…

    C# 2023年5月31日
    00
  • 在Asp.net core中实现websocket通信

    在ASP.NET Core中实现WebSocket通信的完整攻略如下: 步骤一:创建ASP.NET Core Web应用程序 首先,我们需要创建一个ASP.NET Core Web应用程序。可以使用Visual Studio或者命令行工具创建一个新的ASP.NET Core Web应用程序。 步骤二:添加WebSocket中间件 在ASP.NET Core中…

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