Deff_Dev

[Unity/C#] 일반화 프로그래밍 (Generic) 본문

Unity(유니티)/유니티 공부

[Unity/C#] 일반화 프로그래밍 (Generic)

Deff_a 2024. 6. 29. 19:16

일반화란 ?

- 특수한 개념으로부터 공통된 개념을 찾아 묶는 것

 

이러한 일반화를 이용하는 프로그래밍이 일반화 프로그래밍 !

 

일반화 프로그래밍의 일반화 대상 ▶ 데이터 형식 (Data Type)


일반화 함수

예를 들어 배열을 복사하는 함수가 있다고 해보자.

 

int 형의 배열을 복사할 수 도 있지만 float, string 등 다른 형태의 배열도 복사를 해야 될 때, 해당 함수를 오버로딩해 해결할 것이다.

    public void CopyArr(int [] arr, int [] targetArr)
    {
        for(int i = 0; i< arr.Length; i++)
        {
            targetArr[i] = arr[i];
        }
    }

    public void CopyArr(string[] arr, string[] targetArr)
    {
        for (int i = 0; i < arr.Length; i++)
        {
            targetArr[i] = arr[i];
        }
    }

    public void CopyArr(float[] arr, float[] targetArr)
    {
        for (int i = 0; i < arr.Length; i++)
        {
            targetArr[i] = arr[i];
        }
    }

    // 모든 데이터 형식을 오버로딩 해야한다.

 

이렇게 된다면, 복사해야 될 배열의 데이터 형식이 늘어나면 늘어날수록 오버로딩 함수는 엄청나게 많아질 것이다.

100개의 데이터 타입을 복사해야 한다면 100개의 함수를 만들어야 할 수 도 있다.

 

이때, 일반화 함수를 사용하게 된다면 여러 형식에 맞춰 복사 함수를 오버로딩할 필요가 없어진다.

 

일반화 함수를 작성하는 방법은 다른 일반 함수 작성법과 크게 다르지 않고, 데이터 형식에 T가 들어간다.

 

위 함수를 일반화 함수로 바꿔보자.

    private void Awake()
    {
        int [] arr = new int[4];
        int [] targetArr = new int[4];

        CopyArr<int>(arr, targetArr);
    }

    public void CopyArr<T>(T[] arr, T[] targetArr)
    {
        for (int i = 0; i < arr.Length; i++)
        {
            targetArr[i] = arr[i];
        }
    }

 

일반 함수와 일반화 함수의 차이점은 데이터 형식이 T라는 점, 이름 뒤에 형식 매개변수 <T>가 들어간다는 점이다.

 

함수를 호출할 때, <> 안에 데이터 형식을 지정해준다면 T는 해당 데이터 형식으로 치환되서 함수가 실행된다.

 

이렇게 일반화 함수를 사용한다면, 데이터 형식의 유연하게 처리할 수 있다,


일반화 클래스

클래스의 데이터 형식을 일반화한 클래스이다.

public class CopyArr_Int
{
    private int[] arr;

    public void CopyArr(int[] targetArr)
    {
        for (int i = 0; i < arr.Length; i++)
        {
            arr[i] = targetArr[i];
        }
    }
}
public class CopyArr_String
{
    private string[] arr;

    public void CopyArr(string[] targetArr)
    {
        for (int i = 0; i < arr.Length; i++)
        {
            arr[i] = targetArr[i];

        }
    }
}

 

위처럼 클래스 안에 작성된 기능은 똑같지만 데이터 형식이 다르다고 할 때, 데이터 형식이 추가되면 추가될 수록 클래스가 늘어날 것 이다.

 

이때, 일반화 클래스를 사용해보자 !

public class CopyArr <T>
{
    private T[] arr;

    public void Copy(T[] targetArr)
    {
        for (int i = 0; i < arr.Length; i++)
        {
            arr[i] = targetArr[i];
        }
    }
}

public class Test
{
    public void Ts() // 사용
    {
        CopyArr<int> copy = new CopyArr<int>();

        int[] target = new int[4];

        copy.Copy(target);
    }
}

 

일반화 클래스를 통해 클래스에서도 데이터 형식을 유연하게 처리할 수 있다.

 

일반화 클래스를 이용한 싱글톤 글이다.

 

[Unity/C#] Generic Singleton (제네릭 싱글톤)

게임 개발 할 때, 가장 많이 사용하는 디자인 패턴 중 하나인 싱글톤 패턴을 사용한다고 하면 public static GameManager _Intstance; private void Awake() { _Intstance = this; } 이런식으로 많이 사용했을 것 이다. 

deff-dev.tistory.com


형식 매개변수(T) 제약시키기

 

형식 매개변수 T가 특정 조건에만 대응하도록 제약을 줄 수 있다.

where 형식 매개변수 : 제약 조건

 

일반화 함수나 클래스 이름 뒤에 where 형식 매개변수 : 제약 조건을 작성하면 된다.

 

제약 설명
where T : struct T는 값 형식이어야 한다.
where T : class T는 참조 형식이어야 한다.
where T : new() T는 반드시 매개변수가 없는 생성자가 있어야 한다.
where T : 부모 클래스 이름 T는 명시한 기반 클래스의 파생 클래스여야 한다.
where T : 인터페이스 이름 T는 명시한 인터페이스를 반드시 구현해야 한다. (복수 명시 가능)
where T : U T는 또 다른 형식 매개변수 U로부터 상속받은 클래스여야 한다.

 

where T : 부모 클래스 이름 제약 조건 예제

 

[내배캠 Unity 4기] 정식 캠프 6주차 (1) [Generic 다운캐스팅]

오늘 배운 내용오브젝트 풀을 이용하여 오브젝트 생성을 구현했는데, 하나의 딕셔너리로 오브젝트를 관리하고 GetComponent를 최소화하 위해 오브젝트 풀로 생성되는 각각의 클래스들이 PoolObject

deff-dev.tistory.com

 

struct, class, 인터페이스 이름 제약 조건도 위 글의 기능과 비슷하므로 설명을 생략하겠다.

 

where T : new()

public class Character
{
    string name;
    public Character()
    {
        name = "Deff";
    }
}
public class Test
{
    public void Ts() // 사용
    {
        Character character = CreateInstance<Character>();
    }

    public T CreateInstance<T>() where T : new()
    {
        return new T();
    }
}

 

new()는  매개 변수가 없는 기본 셍성자를 가진 어떤 클래스의 객체라도 생성해준다.

 

where T : U

using UnityEngine;
using System;

public class Base { }
public class Derived:Base { }
public class Add:Base { }
public class BaseArray<U> where U : Base
{
    public U [] Array { get; set; }
    public BaseArray(int size)
    {
        Array = new U[size];
    }
    public void CopyArray<T>(T[] Source) where T : U
    {
        Source.CopyTo(Array, 0);
    }
}

public class Test : MonoBehaviour
{
    private void Start()
    {
        BaseArray<Derived> derArr = new BaseArray<Derived>(3);
        BaseArray<Base> baseArr = new BaseArray<Base>(3);
        
        BaseArray<Derived> derArr2 = new BaseArray<Derived>(3);
        derArr2.CopyArray<Derived>(derArr.Array);
     }
}

 

U는 Base를 상속 받아야 하고,(where U : Base) T는 U를 상속받아야 한다.  (where T : U)