[C#] 참조형 (Reference)
1. 참조 (Reference)란?
앞서 params, ref, out, in 같은 매개 변수를 호출 메서드로 전달하는 키워드에 대하여 알아보았다.
그 중 ref, out, in은 참조로 전달되어 호출 메서드가 전달 받은 매개 변수의 실제 값에 접근할 수 있다고 하였다.
참조란 무엇이길래 매개 변수의 실제값에 접근이 가능한걸까?
C++에는 주소값을 가지는 변수인 포인터가 존재한다.
우리는 포인터를 통해서 해당 주소를 가지는 변수에 직접 접근이 가능했고 참조자(&)를 통해서 안전한 접근도 가능했다.
간단하게 다른 변수의 장소를 가리키는 것을 '참조(Reference)' 라고 한다. (참조는 객체의 '별칭' 이라고도 한다.)
ref, out, in 키워드도 해당 변수의 주소에 접근함으로 실제값에 접근 가능한 것이다.
2. C#의 참조형
C#은 참조가 포인터를 완벽히 대체하며, 그로 인해 포인터 자료형을 일반적으로는 사용하지 않는다.
object, string, dynamic, array는 C#의 기본 참조 형식이다.
참조 형식답게 포인터를 통해 다른 객체의 장소를 가리키는 방식이다.
이들은 모두 스택(Stack) 영역에 주소가 저장되고, 이를 통해 힙(Heap)에 저장된 값에 접근한다.
string은 문자열을 저장하는 기능을 한다.
string는 new 키워드 없이 큰 따옴표(" ")만을 이용하여 생성할 수 있고 +연산자를 이용해 서로 더할 수 있다.
object는 모든 클래스 중에 기본 클래스로 모든 데이터 타입의 루트형이다.
object는 모든 데이터형을 참조할 수 있지만, 해당 데이터 형의 특성을 잃어 해당 데이터형의 특성을 살리기 위해선
캐스팅을 필요로한다. 바꿔말하면 어떤 형으로든 캐스팅 가능하다.
dynamic 또한 object와 비슷하게 어떤 형으로도 캐스팅이 가능하다.
object와 다른 점은 데이터 형의 특성을 그대로 유지해 해당 데이터형의 특성을 그래도 활용할 수 있다.
하지만, 런타임 시 타입이 결정되기 때문에 다른 형식보다 느리다.
위 세가지 타입은 값을 할당하기만 해도 새로운 인스턴스를 생성하기에
다른 메서드에 실수로 접근하는 일이 줄어든다.
하지만, 배열은 배열 내 요소에 접근 시 참조 중인 메서드에 영향을 주게 되니
반드시 new를 통해 인스턴스를 생성해주어야 한다.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace params__ref__in__out
{
class Example1
{
// array
public int[] a = { 1, 2, 3, 4, 5 };
public int[] b;
}
class Example2
{
// string
public string a = "스트링입니다.";
public string b;
}
class Example3
{
// object
public object a = "다이나믹도 새로 생성됩니다.";
public object b;
}
class Example4
{
// class
public int a = 10;
}
class Program
{
static void Main(string[] args)
{
// 인스턴스 생성
Example1 ex1 = new Example1();
// 객체 참조
ex1.b = ex1.a;
Console.WriteLine("{0}, {1}", ex1.a[1], ex1.b[1]);
// b의 값 변경
ex1.b[1] = 10;
Console.WriteLine("{0}, {1}", ex1.a[1], ex1.b[1]);
// 인스턴스를 새로 생성하면 연결이 끊어진다.
ex1.b = new int[]{ 6, 7, 8, 9, 10 };
ex1.b[1] = 30;
Console.WriteLine("{0}, {1}", ex1.a[1], ex1.b[1]);
// 인스턴스 생성
Example2 ex2 = new Example2();
Console.WriteLine("{0}", ex2.a);
// 객체 참조
ex2.b = ex2.a;
// 문자열은 new를 사용하지 않아도 새 객체를 생성하는 것으로 취급한다.
ex2.b = "저는 새로 생성됩니다.";
Console.WriteLine("{0}, {1}", ex2.a, ex2.b);
// 인스턴스 생성
Example3 ex3 = new Example3();
Console.WriteLine("{0}", ex3.a);
// 객체 참조
ex3.b = ex3.a;
Console.WriteLine("{0}, {1}", ex3.a, ex3.b);
// b의 값 변경. dynamic은 타입 캐스팅도 가능하다.
ex3.b = 777;
Console.WriteLine("{0}, {1}", ex3.a, ex3.b);
// 인스턴스 생성
Example4 ex4_1 = new Example4();
// 객체 참조
Example4 ex4_2 = ex4_1;
Console.WriteLine("{0}, {1}", ex4_1.a, ex4_2.a);
// ex4_2의 값 변경
ex4_2.a = 20;
Console.WriteLine("{0}, {1}", ex4_1.a, ex4_2.a);
}
}
}
2, 2
10, 10
10, 30
스트링입니다.
스트링입니다., 저는 새로 생성됩니다.
다이나믹도 새로 생성됩니다.
다이나믹도 새로 생성됩니다., 다이나믹도 새로 생성됩니다.
다이나믹도 새로 생성됩니다., 777
10, 10
20, 20
C# Class 또한 선언 시 참조형으로 선언된다. 그 후 new를 이용해 인스턴스를 할당하는 방식이다.
하지만, 구조체는 값형이다. 그래서 선언만 해주면 그 즉시 인스턴스가 생긴다.
// 클래스는 참조형이니 new를 반드시 사용.
ExClass exc = new ExClass();
// 구조체는 사용할 필요 X
ExStruct exs;
클래스는 참조 형식이니 대입을 할 경우 스택에 있는 인스턴스의 참조값(주소) 만 복사하여 대입을 한다.
즉, 4byte의 값만 복사가 일어나 굉장히 가볍다.
구조체는 값형이기 때문에 대입할 경우 구조체 용량 전체에 대한 복사가 이루어지므로 클래스와 비교해서 무겁다.
따라서 가급적 구조체는 크지않은 데이터에 대해서 사용하는 편이 좋다.
다만, 값형은 속도가 빠른 스택 영역에 저장되고 참조형은 상대적으로 느린 힙 영역에 저장되므로
객제의 크기가 작다면 구조체가 유리하고 객체의 크기가 크다면 클래스가 유리하다.
http://daplus.net/c-c-%EB%AC%B8%EC%9E%90%EC%97%B4-%EC%B0%B8%EC%A1%B0-%EC%9C%A0%ED%98%95/