C# - Generic

윤형·2025년 1월 12일
0

Unity

목록 보기
5/8

서론

우리가 유니티에서 C#스크립트를 생성하면 기본적으로 3개의 네임스페이스가 추가되는 것을 볼 수 있다.

System.Collection, System.Collection.Generic 그리고 UnityEngine이다.

오늘은 이중에서 collection과 제너릭에 대해서 설명해보고자 한다.

Collection

같은 성격을 가진 데이터 모음을 담는 자료구조

제너릭을 쉽게 이해하기 위해서는 먼저 Collection이라는 것을 알아야한다. Collection은 C# 1.0에서 등장한 개념이다. 따라서 매우 오래된 문법중 하나이다. Collection은 쉽게 설명하면 배열(array)이라고 생각하면 된다.

Collection Type

  • ArrayList
  • HashTable
  • Queue
  • Stack

Collection에는 대표적으로 4가지가 존재한다. ArrayList는 기존 array의 크기할당 문제를 보완하기 위해 우리가 흔히 List라고 편하게 사용하는 자료구조의 원형이라고 생각하면 편할것 같다. stack과 queue는 자료구조를 공부하면 기본으로 나오는 개념이기 때문에 설명은 넘어가도록 하겠다.

ArrayList list = new ArrayList();

list.add(10);
list.add(15);
list.add("Hello");

Collection을 타고 들어가면 매개변수를 object로 받는것을 확인할 수 있다. 따라서 int형과 string형 둘다 add가 가능하다는 것을 볼 수 있다. 하지만!! 지난시간에 우리는 object 자료형의 boxing, unboxing 문제점을 확인했었다. 따라서 Collection을 사용하면 불필요한 메모리가 힙 영역에 계속 쌓이는 문제점이 있게 된다.

Hashtable ht = new Hashtable();
ht["Book1"] = "일기";
ht["Book2"] = "공부 노트";
ht[1.5f] = 15;

해시테이블도 마찬가지로 object를 받기 때문에 여러 자료형을 동시에 사용해도 문제가 없지만 역시나 언박싱 문제가 있어 성능이 저하된다. 따라서 이를 보완한게 제너릭이다.

결론적으로는, 기존 Collection은 문제점이 있어 C# 2.0버전에서 이를 보완한 Generic이 등장하였다.


Generic

일반화를 이용하는 프로그래밍 기법

제너릭은 C# 2.0버전에서 등장한 개념이다. Collection의 단점을 해결한 문법이라고 생각하면 좋을 것 같다.

public void Copy(int[] a, int b[]){
	for(int i=0;i<a.Length();i++){
    	b[i] = a[i];
    }
}

public void Copy(float[] a, float[] b){
	for(int i=0;i<a.Length();i++){
    	b[i] = a[i];
    }
}

여기 이렇게 매개변수를 2개를 받고 복사하는 메서드가 있다고 생각해보자, 만일 인자의 자료형이 int, float... 이런식으로 여러개가 존재한다면, 오버라이딩을 통해 구현할 수 있다.

public void Copy(object[] a, object[] b){
	for(int i=0;i<a.Length();i++){
    	b[i] = a[i];
    }
}

기존 object를 사용하여 이렇게 한개의 메서드로 모든걸 커버칠 수 있지만 앞서 설명했듯, 언박싱 문제가 있다. 그렇다면 제너릭을 사용하면 어떻게 할 수 있을까?

제너릭 함수

public void Copy<T>(T[] a, T[] b){
	for(int i=0;i<a.Length();i++){
    	b[i] = a[i];
    } 
}

이렇게 작성해줄 수 있다. 여기서 T는 형식 매개변수라고 불리고 제너릭 자료형의 나타내는 것이라고 생각하면 된다. 즉, 어떤 자료형인지를 알려주는 역할이라고 생각하면 된다.
형식 매개변수를 대입해 메서드를 실행하게되면 입력받은 자료형으로 자동으로 컴파일 되어서 사용할 수 있는것이다. 그런데 <> 모양 어디서 많이 보지않았는가?

GetComponent<Image>().sprite = "";

이런식으로 유니티에서 컴포넌트를 관리할때 자주 등장하던 녀석이다. 그동안은 그냥 이렇게 사용하는구나~ 라는 식으로 넘어갔었는데, 사실은 제너릭이였던 것이다!!

제너릭 클래스

클래스들을 일반화(제너릭) 하는 방법은 다음과 같다.

public class return_Generic<T>
{
	private T num;
    public T Return()
    {
    	return num;
    }
}

이렇게 작성해줄 수 있다. 이걸 호출해서 사용하는 방법은

return_Generic<int> test = new();

이런식으로 제너릭 메서드를 호출할때 형식 매개변수에 원하는 자료형을 넣고 호출하면 된다. 이렇게 되면 int든 sting이든 모든 자료형에 대해서 다 할 수 있는 것이다.

그렇다면 뭔가 형식 매개변수 자료형에 제약을 걸어서 원하는 결과만 받고싶으면 어떻게 하면 될까? (예를 들어 숫자만 받기)

바로 뒤에 제약을 걸어주는 것이다.

public class Test<T> where T : struct
{
	public void Add(T num1, T num2){
    	T sum = num1 + num2;
        return sum;
    }
}

이렇게 사용할 수 있다. 제약에는 여러가지 가 있다.

제약설명
where T : structT는 값 형식
where T : classT는 참조 형식
where T : new()T는 매개변수가 없는 생성자 필요
where T : 부모 클래스 이름T는 명시한 부모 클래스의 자식 클래스
where T : 인터페이스 이름T는 명시한 인터페이스 구현
where T : MaskableGraphic마스크(Mask) 처리가 가능한 그래픽 요소
where T : UT는 또 다른 형식 매개변수 U로부터 상속

Collection -> 제너릭

Collection에서 나왔던 자료구조들을 일반화하면 어떤 모양일까?

ArrayList

ArrayList를 제너릭화 하면 List<T> 이렇게 사용할 수 있다.

List<int> list = new List<int>();
list.Add(1);

이렇게 사용하면 된다.

HashTable

HashTable을 제너릭화 하면 Dictionary<Tkey, Tvalue> 이렇게 사용할 수 있다.

Dictionary<string, string> dic = new();
dic["첫번째"] = "one";

이렇게 사용하면 된다.

Stack

Stack을 제너럴화 하면 Stack<T> 이다.

Stack<int> stack = new Stack<int>();
stack.Push(10);
stack.Push(20);
Stack.Pop();

이렇게 사용할 수 있다.

Queue

Quque를 제너럴화 하면 Queue<T>이다.

Queue<int> queue = new Quque<int>();

queue.Enqueue(10);
queue.Enqueue(20);

queue.Dequeue();

이렇게 사용하면 된다.

참고자료

Youtube - 게이머TV

본 포스팅은 상업적 목적을 포함하지 않습니다.

profile
제가 관심있고 공부하고 싶은걸 정리하는 저만의 노트입니다.

0개의 댓글