[C#] 제네릭

AsiaticRicecake·2025년 3월 26일

1. 📖 object 형식

C#의 존재하는 모든 클래스의 기본이 되는 자료형을 말하는데
object 형식을 사용하면 데이터의 형식에 관계없이 object 형식의 자료형에 담을 수 있습니다.

그렇지만 참조 형식으로만 저장하여 해당 데이터를 힙에 할당하고, 주소 값만 담고 있기 때문에, 데이터를 다시 활용하기 위해서는 값 형식으로 변환하는 과정이 필요합니다.

  // object 자료형 (모든 자료형을 담을 수 있다!)
  object obj1 = 1;
  object obj2 = "안녕";
  object obj3 = true;
  object obj4 = 1.54;

object 형식은 모든 자료형의 최상위 부모라고 생각하시면 됩니다.
즉, 상속과 마찬가지로 기본적으로 다운캐스팅이 안되기 때문에 명시적으로 변환해야 합니다

object left = 10;
object right= 10;

int result = left + right; // 구문 오류가 뜸
object left = 10;
object right = 10;

int result = (int)left + (int)right; // (int)를 붙여 명시적으로 변환함

Console.WriteLine(result);

1-1 🔖 박싱

값 형식에서 참조형식으로 변환하는 과정을 박싱이라고 합니다.
어떤 크기를 필요로 할지 몰라 데이터를 복사해 힙 메모리에 할당합니다.

int value = 10;

// 박싱 : 값형식 → 참조형식
object obj = value;

1-2 🔖 언박싱

박싱과 반대되는 개념으로 참조 형식에서 값 형식으로 변환하는 과정을 언박싱이라고 합니다.
데이터를 복사해 스택 메모리에 할당하는 방식이며 명시적으로 선언해야 사용 가능합니다.

int value = 10;

// 박싱 : 값형식 → 참조형식
object obj = value;
  
// 언박싱 : 참조형식 → 값형식
int result = (int)obj;

박싱과 언박싱을 과정을 거치게 되면 엄청 느려져 성능하락으로 이어지기 때문에
왠만하면 최적화를 위해서는 피하는 것이 좋습니다.

2. 📖 제네릭 (일반화)

object 형식을 이용한 형변환은 박싱과 언박싱 과정을 거치면서 성능 저하가 있고 해당 형식으로의 변환이 적절한지에 대한 판단없이 진행되므로 코드가 위험해질 수 있기 때문에 피해야 한다고 했습니다.

제네릭은 박싱과 언박싱 단계를 거치지 않고 형식을 조정하므로 성능이 향상되고, 컴파일 단계에서 올바른 데이터 형식이 적용되므로 형식 안정성이 보장됩니다.

 static void Main(string[] args)
{
 int a = 10;
 int b = 20;

 Util.Swap(ref a, ref b);

 Console.WriteLine("a = {0}, b = {1}", a, b);
}


public class Util
{
    public static void Swap(ref int left, ref int right)
    {
        int temp = left;
        left = right;
        right = temp;
    }    
}
a = 20, b = 10

이 코드에서 다른 자료형으로 바꿀 때 함수를 더 만들 수도 있고 object를 사용할 수도 있지만
상당히 비효율적일 수 밖에 없기 때문에 제네릭을 사용해야 합니다.

2-1 🔖 일반화 함수

사용할 때 <>를 사용하여 배치합니다.
보통 프로그래밍 국룰로 T를 사용한다고는 하는데 아무 글자 사용해도 동작은 합니다.

 public static void Swap<T>(ref T left, ref T right)
 {
     T temp = left;
     left = right;
     right = temp;
 }    

Main에는 바꾸고자 하는 자료형을 <>에 적어주면 됩니다.

static void Main(string[] args)
{
	int iLeft = 10;
	int iRight = 20;
	Util.Swap<int>(ref ileft, ref iright);
 	Console.WriteLine("a = {0}, b = {1}", iLeft, iRight);

	float fLeft = 10.56;
	float fRight = 20.87;
	Util.Swap<float>(ref fleft, ref fright);
 	Console.WriteLine("a = {0}, b = {1}", fLeft, fRight);
    
    bool bLeft = true;
	bool bRight = false;
	Util.Swap<bool>(ref bLeft, ref bRight);
 	Console.WriteLine("a = {0}, b = {1}", bLeft, bRight);
    
}
a = 20, b = 10
a = 20.87, b = 10.56
a = False, b = True

2-2 🔖 일반화 자료형 제약

다만 일반화는 기본적으로 산술연산, 비교연산, 논리연산을 못합니다. 대입연산만 가능합니다.

 public static T Add <T>(T left, T right)
 {
     return left + right;  // 오류
 }

where을 통해 제약을 걸어 구조체만, 클래스만 사용가능하도록 제한하는 기능입니다.

public static void Test<T>(T value) where T : struct
{

}
// 구조체만 사용 가능
public static void Test<T>(T value) where T : struct { }           

// 클래스만 사용 가능
public static void Test<T>(T value) where T : class { }             

// T열거형만 사용 가능
public static void Test<T>(T value) where T : Enum { }              

// 매개변수 없는 생성자가 있는 자료형만 사용 가능
public static void Test<T>(T value)where T : new() { }  

// 인터페이스를 포함한 자료형만 사용 가능
public static void Test<T>(T value) where T : IComparable { }   

0개의 댓글