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# Directory.GetFiles(string path):获取指定目录下的所有文件路径

    Directory.GetFiles(string path) 是C#中的一个静态方法,它返回指定目录中文件的名称,包括该目录中的所有子目录。它是 DirectoryInfo 类的一个实例方法 GetFiles 的静态等效方法。 方法签名 public static string[] GetFiles(string path); public static …

    C# 2023年4月19日
    00
  • c#二叉树存储介绍

    下面是“c#二叉树存储介绍”的详细攻略。 1. 什么是二叉树 二叉树是一种非常常见的数据结构,它由若干个节点构成,每个节点最多只有两个子节点,由此得名。二叉树有很多种形态,比如完全二叉树、满二叉树、平衡二叉树等等。 2. 二叉树的存储方式 二叉树有两种常见的存储方式:链式存储和数组存储。链式存储是指用指针来表示二叉树中的节点之间的关系,它比较灵活,但是需要额…

    C# 2023年6月7日
    00
  • C#中把Datatable转换为Json的5个代码实例

    在C#中,将DataTable转换为JSON格式是一种常见的操作。本文将介绍5个将DataTable转换为JSON的代码实例,并提供两个示例程序。 示例一:使用Newtonsoft.Json库将DataTable转换为JSON 以下是一个使用Newtonsoft.Json库将DataTable转换为JSON的示例: using Newtonsoft.Json…

    C# 2023年5月15日
    00
  • C#中字符串的一般性和特殊性

    C#中字符串的一般性和特殊性 如果你正在学习C#,字符串(string)是一个基础重要的数据类型。在本文中,我们将介绍C#中字符串的一般性和特殊性,以及在实际编程中如何使用它们。 C#中字符串的一般性 字符串的定义 在C#中定义字符串变量的语法格式为: string variableName; 其中,variableName为字符串变量的名称。可以使用赋值运…

    C# 2023年6月8日
    00
  • Asp.Net Core实现Excel导出功能的实现方法

    在本攻略中,我们将详细讲解Asp.Net Core实现Excel导出功能的实现方法,并提供两个示例说明。 安装EPPlus:首先,我们需要安装EPPlus NuGet包。我们可以使用Visual Studio的Get包管理器来安装EPPlus,或者在项目文件(.csproj)中手动添加EPPlus的NuGet包引用。例如: <ItemGroup>…

    C# 2023年5月16日
    00
  • C#实现抓取和分析网页类实例

    C#实现抓取和分析网页类实例 简介 抓取和分析网页是当今互联网应用领域必不可少的一项技术。C#语言提供了很多抓取和分析网页的方式,我们可以选择对应的库和框架,快速实现我们的需求。 本文将介绍C#实现抓取和分析网页的完整攻略,包含以下内容: C#抓取网页的基础知识 C#使用WebRequest和HttpWebRequest抓取网页的方法 C#使用HtmlAgi…

    C# 2023年6月7日
    00
  • C#中Write()和WriteLine()的区别分析

    C#中Write()和WriteLine()的区别分析 在C#编程中,我们常常会用到Write()和WriteLine()两个方法来输出文本内容。它们的使用方式和输出结果都有些不同,下面我们对它们进行区别分析。 Write()方法 Write()方法是用来输出文本的,它会将输出的文本放在同一行上。 使用方法 我们来看一个基本的输出示例: Console.Wr…

    C# 2023年6月1日
    00
  • 分享一个asp.net pager分页控件

    Asp.NetPager是一个.NET平台上的分页控件,可以方便地实现分页功能。以下是使用Asp.NetPager实现分页功能的完整攻略。 环境准备 在使用Asp.NetPager前,需要安装Asp.NetPager包。可以使用以下命令来安装Asp.NetPager: Install-Package AspNetPager 实现分页功能 以下是使用Asp.N…

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