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# WPF如何实现滚动显示的TextBlock

    要实现滚动显示的TextBlock,可以使用WPF中的ScrollViewer和TextBlock结合使用。ScrollViewer是一个滚动视图容器,可以将其在需要滚动的控件周围包装起来,从而实现滚动效果。 下面是实现过程: 第一步:在XAML文件中,在需要滚动显示的TextBlock周围加入ScrollViewer容器,同时设置VerticalScrol…

    C# 2023年6月6日
    00
  • C#调用python.exe使用arcpy方式

    下面是详细讲解“C#调用python.exe使用arcpy方式”的完整攻略。 一、前置准备 在 C# 中调用 Python 脚本需要借助于 Process 类,同时需要安装好 python 的开发环境以及第三方库 arcpy。 安装 arcpy: 安装 ArcGIS Desktop 或者 ArcGIS Engine。 执行 ArcGIS Desktop 安装…

    C# 2023年5月15日
    00
  • C#一个简单的定时小程序实现代码

    下面是详细的讲解“C#一个简单的定时小程序实现代码”的完整攻略。 1. 设计思路 我们需要实现一个简单的定时小程序,应该首先考虑怎样实现计时功能。在C#中,常见的计时方式有两种:一种是使用System.Timer类,另一种是使用System.Threading.Timer类。这两种方式都可以实现定时器的功能,但有一定的差别。下面我们将分别介绍这两种方式的使用…

    C# 2023年5月31日
    00
  • ASP.NET(C#) 面试总结面试题大全

    标题规范: 在markdown中,标题通过在文本前添加#号表示。一级标题需要1个#号,二级标题需要2个#号,以此类推。例如,一级标题的写法为: # 一级标题 二级标题的写法为: ## 二级标题 代码块规范: 在markdown中,代码块通过使用三个`来表示代码块的开始和结束,示例如下: public static void main(String[] arg…

    C# 2023年5月14日
    00
  • 通用 HTTP 签名组件的另类实现方式

    以下是“通用HTTP签名组件的另类实现方式”的完整攻略: 什么是通用HTTP签名组件 通用HTTP签名组件是一种用于生成HTTP签名的组件,它可以帮助发送HTTP请求时验证请求的合法性。通用HTTP签名组件通常用于API认证和授权。 传统的通用HTTP签名组件实现方式 传统的通用HTTP签名组件实现方式通常是在HTTP请求头中添加签信息。以下是一个示例: G…

    C# 2023年5月12日
    00
  • 解读ASP.NET 5 & MVC6系列教程(14):View Component

    下面我来为你详细讲解 ASP.NET 5 & MVC6 系列教程中的第 14 篇文章——View Component 的完整攻略。 一、View Component 简介 View Component 是在 ASP.NET Core 中新增加的基于 HTTP 特性的 MVC 元素。它的主要功能是可以自定义组件部件并将其添加到页面中。与使用Partia…

    C# 2023年5月31日
    00
  • C#常用GDI+文字操作汇总

    C#常用GDI+文字操作汇总 简介 GDI+是微软提供的图像编程接口,被广泛应用于.Net框架下的Windows图形化程序开发中,其中文字操作是常见需求之一。本篇文章将介绍一些常用的GDI+文字操作技巧,包括字体、颜色、对齐方式、文本布局、阴影等。 字体 在GDI+中,字体是一个关键的概念,有许多使用字体的属性可调整。下面是一些常用的字体属性。 //创建新字…

    C# 2023年5月31日
    00
  • asp.net SharpZipLib的压缩与解压问题

    下面我将详细介绍关于“asp.net SharpZipLib的压缩与解压问题”的完整攻略。 什么是 SharpZipLib SharpZipLib 是 .NET 平台下使用的一个流行的压缩库,支持 Gzip、Deflate、BZip2 等多种压缩格式,并且它是在 zlib 许可证下发布的,因此免费且开源。 SharpZipLib 安装 在 Visual St…

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