[c#基础]ICloneable接口

摘要

该接口使你能够创建现有对象的副本的自定义的实现。该接口只提供了,一个Clone方法,实现对象的浅拷贝。有浅拷贝,那么就有相对应的深拷贝。但该接口并没有对我们提供,需要我们自己实现。

什么是浅拷贝与深拷贝?

浅拷贝

将对象的字段复制到新的对象副本中,同时将字段的值也复制过去,但引用类型值复制引用,而不是引用类型本身,也就是,如果源对象的引用类型的字段的值改变了,拷贝的对象的对应的引用类型的字段也会跟着变化。

深拷贝

将对象的字段复制到新的对象副本中,无论是值类型还是引用类型的字段,都会复制类型本身及值。但,源对象的值变化,并不会影响副本中的对应的值。

一个例子

浅拷贝

using Newtonsoft.Json;
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

namespace Wolfy.CloneDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("创建新对象:");
            Person p = new Person() { Name = "wolfy", Id = 1, Address = new Address { City = "北京", Details = "北京 海淀区" } };
            Console.WriteLine(p.ToString());
            Console.WriteLine("克隆对象:");
            Person p2 = (Person)p.Clone();
            Console.WriteLine(p2.ToString());
            var result = object.ReferenceEquals(p, p2);
            Console.WriteLine("p ReferenceEquals p2:" + result);
            Console.WriteLine("此时修改p对象,是否会影响p2对象?");
            p.Name = "wolfy2";
            p.Id = 2;
            p.Address.City = "上海";
            Console.WriteLine("p:" + p.ToString());
            Console.WriteLine("p2:" + p2.ToString());

            Console.Read();
        }

    }
    [Serializable]
    class Person : ICloneable
    {
        /// <summary>
        /// 值类型
        /// </summary>
        private int _id;
        /// <summary>
        /// 特殊的引用类型
        /// </summary>
        private string _name;
        private Address _address;
        public int Id { get => _id; set => _id = value; }
        public string Name { get => _name; set => _name = value; }
        public Address Address { get => _address; set => _address = value; }

        public override string ToString()
        {
            return JsonConvert.SerializeObject(this);
        }
        public object Clone()
        {
            /*
             MemberwiseClone方法创建的新对象,然后将当前对象的非静态字段复制到新的对象创建的浅表副本。
             如果字段是值类型,则执行字段的按位复制。 如果字段是引用类型,引用将复制,但被引用的对象不;
             因此,原始对象和其克隆引用同一对象。
             例如,考虑对象称为 X 引用对象 A 和 B,反过来,引用对象 c。
             X 的浅表副本创建新的对象 X2 也引用对象 A 和 b。与此相反,
             X 的深层副本创建新对象 X2 引用 A2 和 B2,
             是的一个副本的新对象并 B.B2,
             反过来,引用新对象 C2,这是 C 的副本。该示例说明浅和深层复制操作之间的差异。

             有很多方法可以实现深层复制操作,如果浅表复制操作由MemberwiseClone方法并不满足你的需求。
             这些要求包括:

             调用类构造函数要复制可以使用来自第一个对象的属性值创建第二个对象的对象。
             这假定,对象的值完全由其类构造函数中定义。

             调用MemberwiseClone方法创建一个对象,
             对象的浅表副本并将其值是与原始对象的任何属性或其值是引用类型的字段相同的新对象。
             DeepCopy方法在示例中演示了此方法。

             序列化对象是较深复制,,然后将序列化的数据还原到另一个对象变量。

             使用具有递归反射来执行深层复制操作。
             */
            return this.MemberwiseClone();
        }
        public object DeepClone()
        {
            using (Stream objectStream = new MemoryStream())
            {
                IFormatter formatter = new BinaryFormatter();
                formatter.Serialize(objectStream, this);
                objectStream.Seek(0, SeekOrigin.Begin);
                return formatter.Deserialize(objectStream) as Person;
            }
        }
    }
    [Serializable]
    class Address
    {
        private string _city;
        private string _details;

        public string City { get => _city; set => _city = value; }
        public string Details { get => _details; set => _details = value; }
    }

}

如上图所示,浅拷贝,值类型的并不会相互影响,但是引用类型的Address会跟着改变。浅拷贝,在对引用类型的字段时,会拷贝指向该对象的引用。

深拷贝

 Person p2 = (Person)p.Clone();

改为

 Person p2 = (Person)p.DeepClone();

结语

 在实际项目中,这个接口自己是没用过。在看c#相关的文章的时候,看到了该接口的介绍,一篇文章了解下。如果非要说应用场景的话,比如,如果一个方法参数是一个引用类型,你需要在方法中对其进行操作,但又不想改变它原有的值,可以实现该接口,在clone实现深拷贝,对拷贝的副本进行操作,不影响它的原有的值,返回一个新的对象。

posted @ 2018-05-25 13:51  wolfy  阅读(1964)  评论(1编辑  收藏  举报