[C#] 깊은 복사(Deep Copy)와 얕은 복사(Shallow Copy)

Running boy·2023년 8월 6일
0

컴퓨터 공학

목록 보기
14/36

깊은 복사/얕은 복사(Deep Copy/Shallow Copy)

데이터의 값 전체를 복사하는 것을 '깊은 복사(Deep Copy)'라고 하고, 참조를 복사하는 것을 '얕은 복사(Shallow Copy)'라고 한다.

값 형식의 데이터를 복사하면 깊은 복사, 참조 형식의 데이터를 복사하면 얕은 복사가 일어난다.

struct VectorStruct
{
    public float x;
    public float y;

    public VectorStruct(float x, float y)
    {
        this.x = x;
        this.y = y;
    }
}

class VectorClass
{
    public float x;
    public float y;

    public VectorClass(float x, float y)
    {
        this.x = x;
        this.y = y;
    }
}
VectorStruct vs1 = new VectorStruct(5, 5);
VectorStruct vs2 = vs1; // 깊은 복사
vs1.x = 10;
Console.WriteLine(vs2.x); // 5

VectorClass vc1 = new VectorClass(5, 5);
VectorClass vc2 = vc1; // 얕은 복사
vc1.x = 10;
Console.WriteLine(vc2.x); // 10

참조 형식을 깊은 복사로 복제하는 방법

아쉽게도 C#에서 참조 형식의 완벽한 깊은 복사는 지원하지 않는다.

참조 형식 데이터 내부에 또다른 참조 형식 데이터가 있을 경우 이를 연쇄적으로 추적하여 복제해야 되기 때문이다.

C#에서 지원하는 Copy, Clone은 얕은 복사다.


1. 클래스 내 직접 구현

using System;

public class IdInfo
{
    public int IdNumber;

    public IdInfo(int IdNumber)
    {
        this.IdNumber = IdNumber;
    }
}

public class Person
{
    public int Age;
    public string Name;
    public IdInfo IdInfo;

    public Person ShallowCopy()
    {
       return (Person) this.MemberwiseClone();
    }

    public Person DeepCopy()
    {
       Person other = (Person) this.MemberwiseClone();
       other.IdInfo = new IdInfo(IdInfo.IdNumber);
       other.Name = String.Copy(Name);
       return other;
    }
}

public class Example
{
    public static void Main()
    {
        // Create an instance of Person and assign values to its fields.
        Person p1 = new Person();
        p1.Age = 42;
        p1.Name = "Sam";
        p1.IdInfo = new IdInfo(6565);

        // Perform a shallow copy of p1 and assign it to p2.
        Person p2 = p1.ShallowCopy();

        // Display values of p1, p2
        Console.WriteLine("Original values of p1 and p2:");
        Console.WriteLine("   p1 instance values: ");
        DisplayValues(p1);
        Console.WriteLine("   p2 instance values:");
        DisplayValues(p2);

        // Change the value of p1 properties and display the values of p1 and p2.
        p1.Age = 32;
        p1.Name = "Frank";
        p1.IdInfo.IdNumber = 7878;
        Console.WriteLine("\nValues of p1 and p2 after changes to p1:");
        Console.WriteLine("   p1 instance values: ");
        DisplayValues(p1);
        Console.WriteLine("   p2 instance values:");
        DisplayValues(p2);

        // Make a deep copy of p1 and assign it to p3.
        Person p3 = p1.DeepCopy();
        // Change the members of the p1 class to new values to show the deep copy.
        p1.Name = "George";
        p1.Age = 39;
        p1.IdInfo.IdNumber = 8641;
        Console.WriteLine("\nValues of p1 and p3 after changes to p1:");
        Console.WriteLine("   p1 instance values: ");
        DisplayValues(p1);
        Console.WriteLine("   p3 instance values:");
        DisplayValues(p3);
    }

    public static void DisplayValues(Person p)
    {
        Console.WriteLine("      Name: {0:s}, Age: {1:d}", p.Name, p.Age);
        Console.WriteLine("      Value: {0:d}", p.IdInfo.IdNumber);
    }
}
// The example displays the following output:
//       Original values of p1 and p2:
//          p1 instance values:
//             Name: Sam, Age: 42
//             Value: 6565
//          p2 instance values:
//             Name: Sam, Age: 42
//             Value: 6565
//
//       Values of p1 and p2 after changes to p1:
//          p1 instance values:
//             Name: Frank, Age: 32
//             Value: 7878
//          p2 instance values:
//             Name: Sam, Age: 42
//             Value: 7878
//
//       Values of p1 and p3 after changes to p1:
//          p1 instance values:
//             Name: George, Age: 39
//             Value: 8641
//          p3 instance values:
//             Name: Frank, Age: 32
//             Value: 7878

MemberwiseClone 메서드는 해당 객체를 새로운 메모리에 할당하여 독립적인 객체로 복제한다.

이 과정에서 객체의 멤버도 복제되는데 값 형식 데이터는 제대로 깊은 복사가 되지만 참조 형식 데이터는 얕은 복사가 된다.

따라서 참조 형식의 멤버를 깊은 복사하는 코드를 별도로 구현해야 된다.


2. Serialize/Deserialize 사용

[Serializable]
public class X
{
    public string str;

    public int n;

    public int[] arr;
}

public static class Extensions
{
    public static T DeepClone<T>(this T obj)
    {
        using (MemoryStream stream = new MemoryStream())
        {
            BinaryFormatter formatter = new BinaryFormatter();
            formatter.Serialize(stream, obj);
            stream.Position = 0;

            return (T)formatter.Deserialize(stream);
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        X origin = new X();
        origin.str = "Original";
        origin.n = 1;
        origin.arr = new int[3] { 1, 2, 3 };

        X copy = origin.DeepClone();
        copy.str = "Copy";
        copy.n = 2;
        copy.arr = new int[3] { 3, 2, 1 };

        Console.WriteLine($"Origin str: {origin.str}, Copy str: {copy.str}");
        Console.WriteLine($"Origin n: {origin.n}, Copy n: {copy.n}");
        Console.WriteLine($"Origin arr: {string.Join(' ', origin.arr)}, Copy arr: {string.Join(' ', copy.arr)}");
    }
}

// 출력:
// Origin str: Original, Copy str: Copy
// Origin n: 1, Copy n: 2
// Origin arr: 1 2 3, Copy arr: 3 2 1

참고 자료
Microsoft 공식 문서 - Object.MemberwiseClone 메서드
C#에서 개체의 복사본 만들기

profile
Runner's high를 목표로

0개의 댓글