C#

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

Juan_ 2023. 3. 7. 18:14

C#에서 객체를 복사하는 방법에는 복사와 깊은 복사 두 가지가 있습니다. 얕은 복사는 객체의 참조를 복사하여 같은 객체를 참조하는 경우가 있어 원본 객체와 복사본의 값이 공유될 수 있습니다. 반면, 깊은 복사는 객체의 모든 내용을 복사하여 원본 객체와는 독립적인 새로운 객체를 만듭니다.


얕은 복사 (Shallow Copy)

얕은 복사(Shallow Copy)객체의 참조만 복사하여 같은 객체를 참조하게 합니다. 따라서 원본 객체나 복사본 중 하나를 수정하면 다른 객체도 영향을 받을 수 있습니다.

 

아래는 얕은 복사를 수행하는 예시 코드입니다.

class MyClass {
    public int[] arr;
    public MyClass(int[] arr) {
        this.arr = arr;
    }
}

MyClass obj1 = new MyClass(new int[] { 1, 2, 3 });
MyClass obj2 = obj1;  // 얕은 복사

obj2.arr[0] = 4;
Console.WriteLine(obj1.arr[0]);  // 출력: 4

 

위 코드에서 obj1 객체와 obj2 객체는 같은 메모리를 참조하게 됩니다. 따라서 obj2.arr[0] = 4; 코드로 obj2arr 배열을 수정하면 obj1arr 배열도 같이 수정되어 Console.WriteLine(obj1.arr[0]); 코드에서 4가 출력됩니다.

 

   obj1       obj2
+--------+  +--------+
|  arr   |  |  arr   |
|--------|  |--------|
|   -->--+--+--<--   |
|   [1]  |  |   [1]  |
|   [2]  |  |   [2]  |
|   [3]  |  |   [3]  |
+--------+  +--------+

위의 ASCII 아트는 obj1 객체와 obj2 객체를 얕은 복사할 때의 메모리 상태를 나타냅니다. obj1 객체는 { 1, 2, 3 } 배열을 참조하고 있습니다. obj2 객체를 obj1 객체로 얕은 복사하면, obj2 객체는 obj1 객체와 같은 { 1, 2, 3 } 배열을 참조합니다. 이 때, obj2.arr[0] = 4; 코드로 obj2 객체의 arr 배열을 수정하면, obj1 객체도 같은 배열을 참조하고 있기 때문에 obj1.arr[0] 값도 4로 변경됩니다.

 


깊은 복사 (Deep Copy)

깊은 복사(Deep Copy)객체의 내용을 모두 복사하여 원본 객체와는 독립적인 새로운 객체를 만듭니다. 따라서 한 객체의 수정이 다른 객체에 영향을 주지 않습니다.

 

아래는 깊은 복사를 수행하는 예시 코드입니다.

class MyClass {
    public int[] arr;
    public MyClass(int[] arr) {
        this.arr = (int[]) arr.Clone();  // 배열 복사
    }
}

MyClass obj1 = new MyClass(new int[] { 1, 2, 3 });
MyClass obj2 = new MyClass(obj1.arr);  // 깊은 복사

obj2.arr[0] = 4;
Console.WriteLine(obj1.arr[0]);  // 출력: 1

위 코드에서 obj1 객체와 obj2 객체는 서로 독립적인 객체입니다. 따라서 obj2.arr[0] = 4; 코드로 obj2arr 배열을 수정해도 obj1arr 배열은 영향을 받지 않습니다. 따라서 `Console.WriteLine(obj1.arr[0]);` 코드에서 1이 출력됩니다.

 

C#에서 객체를 깊은 복사하는 방법 중 하나는 ICloneable 인터페이스를 구현하는 것입니다. ICloneable 인터페이스는 Clone 메서드를 정의하고 있으며, 메서드를 구현하여 객체를 복사할 수 있습니다. 다음은 ICloneable 인터페이스를 사용하여 MyClass 클래스를 깊은 복사하는 예시 코드입니다.

class MyClass : ICloneable {
    public int[] arr;
    public MyClass(int[] arr) {
        this.arr = arr;
    }

    public object Clone() {
        int[] newArr = (int[]) arr.Clone();  // 배열 복사
        return new MyClass(newArr);
    }
}

MyClass obj1 = new MyClass(new int[] { 1, 2, 3 });
MyClass obj2 = (MyClass) obj1.Clone();  // 깊은 복사

obj2.arr[0] = 4;
Console.WriteLine(obj1.arr[0]);  // 출력: 1

 

위 코드에서 MyClass 클래스는 ICloneable 인터페이스를 구현하도록 변경되었습니다. Clone 메서드에서는 arr 배열을 복사하여 새로운 배열을 만든 후, 새로운 MyClass 객체를 생성합니다. obj2 객체는 obj1 객체의 깊은 복사본이므로, obj2.arr[0] = 4; 코드로 obj2의 arr 배열을 수정해도 obj1의 arr 배열은 영향을 받지 않습니다.

 

또한, C#에서는 System.Object 클래스의 MemberwiseClone 메서드를 사용하여 얕은 복사를 수행할 수 있습니다. 다음은 MemberwiseClone 메서드를 사용하여 MyClass 클래스를 얕은 복사하는 예시 코드입니다.

class MyClass {
    public int[] arr;
    public MyClass(int[] arr) {
        this.arr = arr;
    }

    public object ShallowCopy() {
        return (MyClass) this.MemberwiseClone();
    }
}

MyClass obj1 = new MyClass(new int[] { 1, 2, 3 });
MyClass obj2 = obj1.ShallowCopy();  // 얕은 복사

obj2.arr[0] = 4;
Console.WriteLine(obj1.arr[0]);  // 출력: 4

위 코드에서 MyClass 클래스는 ShallowCopy 메서드를 정의합니다. 이 메서드에서는 MemberwiseClone 메서드를 호출하여 현재 객체를 얕은 복사한 후, 캐스팅하여 반환합니다. obj2 객체는 obj1 객체의 얕은 복사본이므로, obj2.arr[0] = 4; 코드로 obj2arr 배열을 수정하면 obj1arr 배열도 같이 수정됩니다.

 

   obj1       obj2
+--------+  +--------+
|  arr   |  |  arr   |
|--------|  |--------|
|   -->--+--+-->     |
|   [1]  |  |   [4]  |
|   [2]  |  |   [2]  |
|   [3]  |  |   [3]  |
+--------+  +--------+

위의 ASCII 아트는 obj1 객체와 obj2 객체를 깊은 복사할 때의 메모리 상태를 나타냅니다. obj1 객체는 { 1, 2, 3 } 배열을 참조하고 있습니다. obj2 객체를 obj1 객체로 깊은 복사하면, obj2 객체는 { 1, 2, 3 } 배열을 복사하여 새로운 배열을 참조합니다. 이 때, obj2.arr[0] = 4; 코드로 obj2 객체의 arr 배열을 수정해도, obj1 객체는 이 배열과는 다른 { 1, 2, 3 } 배열을 참조하고 있기 때문에 영향을 받지 않습니다.

 


얕은 복사깊은 복사는 객체를 복사할 때 발생하는 문제를 해결하기 위해 사용됩니다. 얕은 복사는 객체의 참조만 복사하여, 두 객체가 같은 메모리를 참조하게 되는 반면, 깊은 복사는 객체의 모든 값을 복사하여, 두 객체가 서로 다른 메모리를 참조하게 됩니다.

얕은 복사는 단순한 참조 복사로 메모리 절약에 유용하지만, 객체를 수정할 때 원하지 않는 부작용을 일으킬 수 있습니다. 따라서 객체를 수정할 일이 있거나, 복사한 객체의 값을 변경하더라도 원본 객체에 영향을 주지 않아야 하는 경우에는 깊은 복사를 사용해야 합니다.

 

C#에서는 배열, 리스트, 객체 등을 복사하는 방법으로 얕은 복사와 깊은 복사를 모두 제공합니다. 배열과 리스트의 경우 Array.Clone() 메서드나 List<T>.CopyTo() 메서드를 사용하여 깊은 복사를 할 수 있습니다. 객체의 경우 MemberwiseClone() 메서드를 사용하여 얕은 복사를 할 수 있으며, IClonable 인터페이스를 구현하여 객체를 깊은 복사할 수 있습니다.

 

알맞은 복사 방법을 선택하면 메모리 관리와 객체 수정 등의 문제를 효과적으로 해결할 수 있습니다.