티스토리 뷰

Copy

 

개요

프로그래밍에서 Copy에는 Deep Copy(깊은 복사)와 Shallow Copy(얕은 복사) 두가지가 있다.

 

Shallow Copy

object class에 MemberwiseClone을 이용하여 Shallow Copy(얕은 복사)를 진행하였다.(Coffee의 Copy 함수는 는 더 아래 참고.)

class Program
{
    static void Main(string[] args)
    {
        var coffee1 = new Coffee() { Shot = 2, CoffeeBean = new CoffeeBean { Origin = "Brazil"} };
        var coffee2 = coffee1.Copy();
        coffee2.Shot = 1;
        coffee2.CoffeeBean.Origin = "Kenya";

        Console.WriteLine($"Coffee1 - HashCode: {coffee1.GetHashCode()},\tShot: {coffee1.Shot},\tOrigin: {coffee1.CoffeeBean.Origin}");
        Console.WriteLine($"Coffee2 - HashCode: {coffee2.GetHashCode()},\tShot: {coffee2.Shot},\tOrigin: {coffee2.CoffeeBean.Origin}");

        // Output
        // Coffee1 - HashCode: 58225482,   Shot: 2,        Origin: Kenya
        // Coffee2 - HashCode: 54267293,   Shot: 1,        Origin: Kenya
    }
}

출력 결과를 보면, Shot은 의도한대로 Coffee2의 값만 변화하였지만, Origin(원산지)는 의도하지 않게 Coffee1까지 변경되었다. Shallow Copy는 참조 형식의 객체는 

Deep Copy

DeepCopy에는 여러가지 방법이 있다 일반적으로 new를 이용하여 새로운 객체를 생성하는 방법으로 많이 접근하지만 이는 새로운 field를 추가할 때 마다, 관련 카피 함수를 수정해야한다는 번거로움이 있다.

 

직열화/역질렬화를 이용한 DeepCopy 예제

따라서, 나는 Binary형태의 직렬화/역직렬화를 이용하여 DeepCopy를 구현해보았다. 

public static class CoffeeExtensions
{
    public static T DeepCopy<T>(this T source) where T : new()
    {
        if (!typeof(T).IsSerializable)
        {
            // fail
            return source;
        }

        try
        {
            object result = null;
            using (var ms = new MemoryStream())
            {
                var formatter = new BinaryFormatter();
                formatter.Serialize(ms, source);
                ms.Position = 0;
                result = (T)formatter.Deserialize(ms);
                ms.Close();
            }

            return (T)result;
        }
        catch (Exception)
        {
            // fail
            return new T();
        }
    }
}

 해당 확장 메서드를 이용하여 Deep Copy의 출력결과는 아래와 같다. 

class Program
{
    static void Main(string[] args)
    {
        var coffee1 = new Coffee() { Shot = 2, CoffeeBean = new CoffeeBean { Origin = "Brazil"} };
        var coffee2 = coffee1.Copy();
        coffee2.Shot = 1;
        coffee2.CoffeeBean.Origin = "Kenya";

        Console.WriteLine($"Coffee1 - HashCode: {coffee1.GetHashCode()},\tShot: {coffee1.Shot},\tOrigin: {coffee1.CoffeeBean.Origin}");
        Console.WriteLine($"Coffee2 - HashCode: {coffee2.GetHashCode()},\tShot: {coffee2.Shot},\tOrigin: {coffee2.CoffeeBean.Origin}");

        var coffee3 = coffee1.DeepCopy();
        coffee3.Shot = 3;
        coffee3.CoffeeBean.Origin = "Ethiopia";

        Console.WriteLine($"Coffee1 - HashCode: {coffee1.GetHashCode()},\tShot: {coffee1.Shot},\tOrigin: {coffee1.CoffeeBean.Origin}");
        Console.WriteLine($"Coffee3 - HashCode: {coffee3.GetHashCode()},\tShot: {coffee3.Shot},\tOrigin: {coffee3.CoffeeBean.Origin}");
        Console.ReadLine();
        
        // Output
        //Coffee1 - HashCode: 58225482,   Shot: 2,        Origin: Kenya
        //Coffee3 - HashCode: 55530882,   Shot: 3,        Origin: Ethiopia
    }
}

DeepCopy를 이용하니 나의 의도대로 Coffee3의 원산지만 잘 변경되었다. 

* 주의 할것은 직렬화를 사용하기 위해서는 해당 객체에 [Serializable] Attribute가 선언되어야 한다. 

[Serializable]
public class Coffee
{
    public int Shot { get; set; }

    public CoffeeBean CoffeeBean { get; set; }

    public Coffee Copy()
    {
        return (Coffee)this.MemberwiseClone();
    }
}

[Serializable]
public class CoffeeBean
{
    public string Origin { get; set; }
}

 

Reference 

https://docs.microsoft.com/ko-kr/dotnet/api/system.object.memberwiseclone?view=net-6.0 

 

Object.MemberwiseClone 메서드 (System)

현재 Object의 단순 복사본을 만듭니다.

docs.microsoft.com

https://aspdotnet.tistory.com/2122

 

c# 의 shallow copy 와 deep copy 알아보기

c# 의 shallow copy 와 deep copy 알아보기 Object.MemberwiseClone 은 shallow copy 를 생성하며, ICloneable interface 와 함께 사용하면 deep copy 을 얻을 수 있습니다. MemberwiseClone 는 새로운 객..

aspdotnet.tistory.com