C#으로 코딩하다보면 서로 비슷하거나, 같은 부분이 많은 클래스를 복사하고 싶어질 때가 있다.
그럴 때, 아래와 같이 클래스를 복사하는 경우도 존재한다.
using System;
namespace Favor
{
class Test
{
int num;
int num1;
}
internal class Program
{
static void Main(string[] args)
{
Test t = new Test();
t.num = 10;
t.num1 = 20;
Test t2 = t;
}
}
}
이렇게 복사했을 경우, 만약 t2의 값이나 t의 값을 바꾸고 싶어지는 경우도 물론 존재할 것이다.
그럴 때, 아래와 같이 값을 바꾸면 어떤 결과가 출력될까?
using System;
namespace Favor
{
class Test
{
int num;
int num1;
}
internal class Program
{
static void Main(string[] args)
{
Test t = new Test();
t.num = 10;
t.num1 = 20;
Test t2 = t;
t2.num = 30;
Console.WriteLine("{0} , {1}", t.num, t.num1);
Console.WriteLine("{0} , {1}", t2.num, t2.num1);
// 출력값 : 30 , 20
// 30 , 20
}
}
}
결과는 주석처리한 대로, 30, 20 과 30, 20이 출력된다.
분명 나는 t2의 값만 바꿨을 뿐인데, t의 값도 동시에 바뀌었다.
이러한 복사를 얕은 복사(Shadow Copy)라고 한다.
얕은 복사란, 복사하려는 객체의 참조만 복사하는 것을 말한다.
| t.num -------------------- | ┐ | |
| ├-------------------------> | 10 | |
| t2.num ------------------- | ┘ | |
| t.num1-------------------- | ┐ | |
| ├-------------------------> | 20 | |
| t2.num1------------------ | ┘ |
얕은복사는 위의 표와같이 복사되는 것이다.
클래스는 참조형식이다. 따라서 가지고 있는 멤버들을 가리키는 참조형식변수가 메인함수에 인스턴스 되는 것이다.
그렇기 때문에 위와 같은 방법으로 객체를 복사하게되면, 같은 주소에 있는 값을 가리키는 같은 참조형식 변수가 하나 더 생기게 된다.
t2도 t1도 같은 값을 가리키고 있기 때문에, t2의 주소를 참조하여 해당 값을 초기화 하면, 같은 곳을 가리키는 t의 값도 바뀌게 되어 위와같은 현상이 발생한다.
복사하려는 객체가 복사한 객체와 서로 다른 값을 가지게 하려면 어떻게 복사해야할까?
클래스는 참조형식이라 같은 주소를 가리키는 변수를 복사하면, 값도 똑같이 바뀐다고 했었다.
그렇다면 다른 주소를 가리키는 변수를 하나 더 만들면 된다. 다른 주소이지만, 같은 값을 가지도록 말이다.
| t.num -------------------- | ---------------------------> | 10 |
| t2.num ------------------- | ---------------------------> | 10 |
| t.num1-------------------- | ---------------------------> | 20 |
| t2.num1------------------ | ---------------------------> | 20 |
위의 표와 같이 같은 값을 가지는 메모리를 하나 더 할당해서 해당 값을 가리키는 멤버를 선언하면 된다.
위와 같이 복사하는 것을 깊은복사(Deep Copy)라고 한다.
단순히 참조만 복사하여 같은 값을 가지게 하는 것이 아닌, 값이 같은 다른 변수를 하나 더 생성하여 값을 유동적으로 초기화해도 원본의 값을 그대로 둘 수 있도록 한다.
깊은복사의 방법은 아래와 같다.
using System;
namespace Favor
{
class Test
{
int num;
int num1;
public Test DeepCopy()
{
Test newCopy = new Test();
newCopy.num = num;
newCopy.num1 = num1;
return newCopy;
}
}
}
위와 같이 새 객체를 인스턴스한 후, 이미 생성된 객체의 값을 대입하면 각각의 다른 변수가 같은 값을 갖게되는 것이다.
이에 대한 출력값은 아래와 같다.
using System;
namespace Favor
{
class Test
{
int num;
int num1;
public Test DeepCopy()
{
Test newCopy = new Test();
newCopy.num = num;
newCopy.num1 = num1;
return newCopy;
}
}
internal class Program
{
static void Main(string[] args)
{
Test t = new Test();
t.num = 10;
t.num1 = 20;
Test t2 = t.DeepCopy();
t2.num = 30;
Console.WriteLine("{0} , {1}", t.num, t.num1);
Console.WriteLine("{0} , {1}", t2.num, t2.num1);
// 출력값 : 10 , 20
// 30 , 20
}
}
}
t2가 t1의 num값, num1의 값을 가지게 되었다.
참조가 아닌 값을 가지게 되었다는 것이 중요하다.
DeepCopy를 통해 t가 값을 통한 전달(Call by Value)을 통해 t2의 역참조 값에 대입하여 같은 값을 갖게된다.
얕은 복사와 깊은 복사를 다루면서 가장 중요하게 생각할 것, 그리고 또 다시 일깨워진 것은 클래스는 철저히 참조형이라는 사실이다.
참조형과 값형의 차이를 확실히 이해하고 넘어가지 않는다면, 태생부터 근본적으로 참조형인 클래스를 다룸에 있어 쉽지 않을 것이다.
클래스는 객체지향의 꽃이자 핵심이다. 이를 잘 다루기 위해선 멤버들의 형식인 참조 형식을 잘 이해하고 넘어가야겠다.
'C# 일기' 카테고리의 다른 글
| 7. 프로퍼티(Property) (0) | 2024.03.05 |
|---|---|
| 6. 정적 멤버 (static) (0) | 2024.03.05 |
| 4. 생성자 (Constructor) (0) | 2024.03.04 |
| 3. foreach문 (0) | 2024.03.04 |
| 2. 배열 (0) | 2024.03.04 |