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; 코드로 obj2의 arr 배열을 수정하면 obj1의 arr 배열도 같이 수정되어 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; 코드로 obj2의 arr 배열을 수정해도 obj1의 arr 배열은 영향을 받지 않습니다. 따라서 `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; 코드로 obj2의 arr 배열을 수정하면 obj1의 arr 배열도 같이 수정됩니다.
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 인터페이스를 구현하여 객체를 깊은 복사할 수 있습니다.
알맞은 복사 방법을 선택하면 메모리 관리와 객체 수정 등의 문제를 효과적으로 해결할 수 있습니다.
'C#' 카테고리의 다른 글
C#의 컴포넌트 구조란? (0) | 2023.03.13 |
---|---|
대리자(Delegate)와 이벤트(Event) 처리 (0) | 2023.03.08 |
단위 테스트(Unit Testing) 및 TDD(Test-Driven Development) (0) | 2023.03.08 |
Call by reference(참조에 의한 호출)와 Call by value(값에 의한 호출) (0) | 2023.03.07 |
인터페이스(Interface)와 추상 클래스(Abstract class)의 차이 (0) | 2023.03.05 |