이번에 정리할 것은 C#에서 자주 사용되는 핵심 개념인 object, 박싱/언박싱, 그리고 제네릭(Generics)에 대해
개념, 차이점, 사용 사례, 주의사항, 예제 코드 등을 정리한다.
object란?object를 상속함object a = 123; // int
object b = "Hello"; // string
object c = new DateTime(); // DateTime
InvalidCastException)// JSON 역직렬화 후 object 타입으로 받기
string json = "{ \"Name\": \"Alice\", \"Age\": 25 }";
object result = JsonConvert.DeserializeObject(json);
// 타입 확인 후 캐스팅
if (result is JObject jObj)
{
Console.WriteLine(jObj["Name"]);
}
// ASP.NET Core API 예시
[HttpGet]
public object GetData()
{
return new { Message = "Hello", Code = 200 };
}
로 사용한다고한다... 사용안해봤는데 나중에 사용해봐야겠다.
object를 쓸까?값 타입(
int,float,struct)을 참조 타입(object)으로 변환
int x = 10;
object obj = x; // Boxing 발생
x의 값을 힙에 복사하고, obj는 그 참조를 가짐object obj = x; 문장에서 x의 값의 주소로 이동해서 값을 확인하고, 그 값이 System.Int32 구조체고 그렇기 때문에 힙에 System.Int32 구조체 의 값을 할당하고 그 힙에 할당된 주소를 obj에 할당 하므로서 박싱이 일어나 주소가 저장되었다고 말하는 것이다.
object타입으로 박싱된 값을 다시 원래 값 타입으로 변환
int y = (int)obj; // 언박싱
obj)를 언박싱하려고 할 때, 해당 객체를 호출(힙에 저장된 위치 가져와서)해서 해당 값이 형태가 지금 형변환(명시적 형변환)하려고 하는 것과 같은지 확인한다. 그 다음 맞다면 값을 복사하여 형변환을 진행하고 아니라면 오류를 발생 시키는 것이다. 왜 정확한 형변환이 필요하냐면 오류의 발생을 막기 위함도 있지만, 구조체의 형태가 변경되는 이유이기 때문이다. 그래서 아래와 같이 먼저 int로 변환 후에 float로 한번 더 변환을 진행하는 것이 그러한 박싱되어 있던 원래 형태를 변환후 한번더 변환을 진행하는 이유이다. 그리고 obj 변수가 더 이상 필요없거나 사용되지 않는 단계까지 된다면 GC가 자동으로 수거해간다.float z = (float)obj; // ❌ InvalidCastException
float z = (float)(int)obj; // ✅ OK
하나의 클래스/메서드를 다양한 타입으로 재사용할 수 있게 해주는 기능
public void Print<T>(T value)
{
Console.WriteLine(value);
}
| 장점 | 설명 |
|---|---|
| 재사용성 | 타입별로 코드를 반복 작성할 필요 없음 |
| 타입 안정성 | 컴파일 타임에 타입 체크 |
| 성능 향상 | 박싱/언박싱 없음 |
| 가독성 | 코드가 간결해지고 중복 제거 가능 |
// 같은 기능의 일반 오버로드 함수들
public static void Swap(ref int a, ref int b)
{
int temp = a;
a = b;
b = temp;
}
public static void Swap(ref float a, ref float b)
{
float temp = a;
a = b;
b = temp;
}
public static void Swap(ref byte a, ref byte b)
{
byte temp = a;
a = b;
b = temp;
}
// 이러한 식으로, 위의 함수를 대체할 수 있다.
public static void Swap<T>(ref T a, ref T b)
{
T temp = a;
a = b;
b = temp;
}
사용:
int x = 1, y = 2;
Swap<int>(ref x, ref y);
Where 제약 조건where이라는 키워드는 T에 사용할 수 있는 타입의 조건을 제한하는 키워드다.
where을 사용해 타입 안정성을 더 강화할 수 있다.| 제약 조건 | 의미 | 예시 |
|---|---|---|
| where T : class | 참조형 타입만 가능 | string, List, MyClass |
| where T : struct | 값 타입만 가능 | int, float, DateTime, 사용자 정의 struct 등 |
| where T : new() | 매개변수 없는 생성자가 있어야 함 | new T() 가능해야 할 때 |
| where T : BaseClass | 특정 클래스 혹은 그 자식 클래스만 가능 | 다형성 처리 |
| where T : InterfaceName | 특정 인터페이스를 구현한 타입만 가능 | IDisposable, IComparable 등 |
| 여러 제약 조건 | 동시에 적용 가능 (,로 연결) | where T : class, new() 등 |
// 클래스만 매개변수로 받고 싶을 때
public void Log<T>(T data) where T : class
{
Console.WriteLine(data?.ToString());
}
// 구조체만 매개변수로 받으려고 할 때
public T Add<T>(T a, T b) where T : struct
{
return default(T); // 예시용
}
// 특정 인터페이스 정의한 클래스 또는 구조체를 받으려고할 때
public T Max<T>(T a, T b) where T : IComparable<T>
{
return a.CompareTo(b) > 0 ? a : b;
}
| 항목 | object | 제네릭 |
|---|---|---|
| 타입 안정성 | 낮음 (형변환 필요) | 높음 (컴파일 타임 확인) |
| 박싱/언박싱 | 발생함 | 없음 |
| 성능 | 상대적으로 느림 | 빠름 |
| 사용 난이도 | 쉬움 | 다소 복잡 |
| 대표 예 | ArrayList, object, var | List<T>, Dictionary<K,V>, Swap<T>() |
object는 유연하지만 타입 안정성 & 성능 저하의 단점이 존재한다.