Generic이란

Generic은 '타입(Type)을 미리 정하지 않고(포괄적인 상태로 해두고), 그것을 사용할 시점에 타입을 정의'하는 것을 말한다.
*generic : 포괄적인, 총칭의
https://fiftiesstudy.tistory.com/221

제네릭은 앞의 글에 나온 박싱, 언박싱의 단점을 극복하기 위해 만들었다고 한다.

우선 제네릭을 사용하지 않는 사례부터 천천히 예시를 들어보자.

타입이 고정적인 클래스의 단점

public class NoGeneric
{
    public int example;
    
    public void PrintExample()
    {
        Debug.Log(classInt);
    }
}

클래스 NoGeneric이 있다.
Int형 변수인 example을 가지고 있고,
그 example을 출력해주는 PrintExample 메서드를 하나 가지고 있는 클래스이다.

다른 클래스인 Test에서 이 기능을 사용하려면, 이렇게 하면 된다.

public class NoGeneric
{
    public int example;
    
    public void PrintExample()
    {
        Debug.Log(example);
    }
}
public class Test : MonoBehaviour
{

     private void Awake()
    {
        NoGeneric noGeneric = new NoGeneric();
        noGeneric.example = 111;
        noGeneric.PrintExample();
    }
}
-------------------------------
Log: 
111

여기까지는 제너릭을 사용하지 않은 보통의 방법이다.

하지만 PrintExample는 int형만 출력이 가능한데, 나중에 갑자기 추가로 float형을 출력하도록 구현해야한다면?

그때마다 이렇게 따로 함수와 변수를 만들어줘야한다.

public class NoGeneric
{
    public int example;
    public float example2;
    
    public void PrintExample()
    {
        Debug.Log(example);
    }
    public void PrintExample2()
    {
        Debug.Log(example2);
    }
    
}
public class Test : MonoBehaviour
{

     private void Awake()
    {
        NoGeneric noGeneric = new NoGeneric();
        noGeneric.example = 111;
        noGeneric.PrintExample();
        
        NoGeneric noGeneric = new NoGeneric();
        noGeneric.example2 = 3.14f;
        noGeneric.PrintExample2();
    }
}
-------------------------------
Log: 
111
3.14

음 정말 번거롭다.
지금이야 변수하나, 출력하는 한줄만 있는 간단한 코드라서 이렇게 떼울 수 있지만, 코드가 길어질수록 그러기는 힘들다.

타입이 다르다는 이유로 같은 기능의 코드를 여러번 쓴다는건 정말 싫을것같다. 수정이라도 하게된다면....😥

하지만 제네릭을 쓴다면?

Generic 사용 예제

public class YesGeneric<T>
{
    public T example;
    public void PrintExample()
    {
        Debug.Log(example);
    }
}

public class Test : MonoBehaviour
{
    private void Awake()
    {
        YesGeneric<int> genericTestA = new YesGeneric<int>();
        genericTestA.example = 1;
        genericTestA.PrintExample();

        YesGeneric<float> genericTestB = new YesGeneric<float>();
        genericTestB.example = 1.5f;
        genericTestB.PrintExample();

        YesGeneric<string> genericTestC = new YesGeneric<string>();
        genericTestC.example = "test";
        genericTestC.PrintExample();

        YesGeneric<NoGeneric> genericTestD = new YesGeneric<NoGeneric>();
        genericTestD.example = new NoGeneric() { classInt = 10 };
        genericTestD.PrintExample();
    }
}
-------------------------------
Log: 
1
1.5
test
NoGeneric

YesGeneric클래스 뒤에 <T>를 써주고, 담고있는 변수들에는 타입 대신 T 를 써준다.
Test클래스에서 YesGeneric클래스를 사용할때, 선언할 때에 클래스이름 뒤에 <타입>을 넣어주면 된다.

선언부분에서 익숙한 느낌이 든다면, 익숙한게 맞다. List나 Dictionary같은 클래스를 사용할때에도 우리는 이렇게 해왔었다.
ex) List intEx = new List();

아무튼,YesGeneric 클래스 부분을 보면 공백 제외하면 3줄밖에 안된다.
단 3줄로 사용할때에 int로,float으로,string으로, 심지어 클래스인 NoGeneric로도 타입을 넣어서 사용할 수 있다.

정리

항상 퇴근하고 쓰느라고 시간이 늦는다.. 원래 더 길게 쓰고싶었는데ㅠ
이번 글에서는 제네릭을 클래스와 필드 , 메서드에 사용했다.
다음글에서는 생성자와 프로퍼티에도 써먹을 수 있다는걸 이어서 정리하고, 내일 생각나는거 추가로 또 해야겠다.

 

 


 

프로퍼티와 생성자에 제네릭 활용

어제는 클래스에 Generic을 사용해서 예시를 보였었다.
오늘은 Property와 생성자 차례다.

public class YesGeneric<T>
{
    private T example;
    public T Example
    {
        get
        {
            return example;
        }
        set
        {
            example = value;
        }
    }

    public YesGeneric(T value)
    {
        Example = value;
    }

    public void PrintExample()
    {
        Debug.Log(Example);
    }
}

어제 작성했던 코드 YesGeneric를 그대로 활용했다.

기존에 Public 변수였던 example을 private으로 바꿔주었다. 그리고 프로퍼티 Example 을 추가해주었다.

생성자를 이용해 처음 클래스를 선언할때 부터 값을 지정해주면, 다른 클래스에서 사용하고자 할때는 이렇게 사용할 수 있다.

public class Test : MonoBehaviour
{
    private void Awake()
    {
        YesGeneric<string> genericTestA = new YesGeneric<string>("막상 해보니 별거 없다.");
        genericTestA.PrintExample();
    }
}
-------------------------------
Log: 
막상 해보니 별거 없다.

핵심은 어제 내용이고, 그냥 이정도도 같이 활용할 수 있다~ 정도로 알면 될 듯 하다.

커플링(Coupling)이란

커플링 : 두 오브젝트가 잘 알고 지내고 하드하게 연결되어 있는 정도를 뜻한다.

좋은 코드는 커플링이 적은 코드이다.

A 와 B 사이에 커플링이 심하다는 말은 A 와 B 가 서로 엮여있는 정도가 높다는 말이다.
때문에 A 가 없어지면 B 의 코드를 수정하기가 쉽지 않다.

커플링을 줄이면 A 와 B 가 서로 모르는 사이가 되기 때문에 둘 중 하나가 없어져도 코드를 수정하기 수월해지거나 수정할 필요가 없게 된다.

너무 좋은 글이 있어서 잠시 복붙좀 하겠다..


너무 좋은 설명이다.
A클래스에 있는 이벤트에 B와 C에서 수정을 해줄 수 있지만, 실행은 오직 A클래스에서만 할 수 있다.
그렇게되면 B와C가 A와 연결된것은 오직 이벤트 연결뿐이다.

커플링이라는 말 자체에 큰 의미는 없는것같다. 위 예제만으로 이해가 돼버린다.
https://ansohxxn.github.io/
구글링할때 되게 자주보이는 분이다. 디자인도 예쁘고 볼때마다 참 글을 잘 정리해서 쓰신다.

.

.

.
오늘 회사에서 CTO님이 내 일을 도와주셨는데, 내가 정확히 어떤 문제에 부딪혔는지도 제대로 설명을 못했었다.

이게 애매하게 알고있어서 설명을 제대로 못하는건지,
그냥 내가 말을 조리있게 못하는건지, 둘 다 인건지 모르겠다.

책을 좀 읽어야겠다.

 


작성일: 2022년 12월 14일

타입

데이터 타입은 값(Value)타입과 참조(Reference)타입으로 나뉜다.

값 타입

Int, Float, Bool , struct, enum 등이 값 타입이다.
변수가 스택에 값 그대로를 할당한다.
new 없이 선언과 동시에 값을 할당할 수 있다.
값 타입은 복사를 하면 값 그 자체가 복사된다.

int a = 10;
int b = 20;
b = a;

debug.Log($"a : {a}");
debug.Log($"b : {b}");

-------------------------------
Log: 
a : 10
b : 10

참조 타입

Class, String , array 등은 참조 타입이다.
변수가 힙에 값이 있는곳의 위치를 할당한다.
참조타입을 사용하기 위해서는 New를 사용해서 초기화 후, 힙에 할당된 메모리를 스택 공간에서 참조해야한다.
참조타입은 복사를 하더라도 그 값이 복사가 되는게 아니라 그 값이 있는곳의 위치가 복사된다.

예시를 이렇게 드는게 맞는지는 모르겠다.

  a = new classTest();
        a.classInt = 10;
        b = a;
        Debug.Log($"a:{a.classInt} b:{b.classInt}");

        b.classInt = 30;

        Debug.Log($"a:{a.classInt} b:{b.classInt}");

-------------------------------
Log: 
a:10 b:10
a:30 b:30

b.classInt = 30; 부분에서
b의 값을 바꿔준게 아니라, b의 위치에 가서 그 값을 30으로 바꿔준것.
근데 그 위치는 a의 위치니까? a도 30으로 나온다..

 


박싱과 언박싱은 일종의 형변환이다.

우선 박싱과 언박싱은 지난 글 값타입, 참조타입에 이어지는 내용이다. 이 한줄은 꼭 기억하자.

값타입은 스택에, 참조타입은 힙에 저장되어있다.

Boxing(박싱)

값타입의 객체를 참조타입으로 변환하는 작업.

int exValue = 100;
object exRef = exValue;

int는 값타입이고, object는 참조타입이다.
위의 코드에서는 참조타입 exRef에 값 타입 exValue을 대입하였다.

스택영역에 있는 exValue값이 exRef로 변환되면서 , 힙영역에 object 형식으로 선언되며 복사되었다.
exRef는 스택영역에 존재하며 , boxed된 exValue의 주소값을 가지고있다.

UnBoxing(언박싱)

참조타입의 객체를 값형식으로 변환하는 작업.

int exValue = 100;
object exRef = exValue;
int exSecondValue = (int)exRef;

한번 박싱한 exRef를 다시 int타입인 exSecondValue에 넣고있다.

언박싱을 할때는 다른타입으로 하거나 해당 타입보다 작은 범위로 변환을 할때는 오류가 발생한다.
그래서 is 연산자를 이용해서 미리 확인을 해야한다.

정리

박싱에는 값을 단순히 참조에 할당하는것보다 최대 20배의 시간이, 언박싱은 할당의 4배의 시간이 소모된다고 한다.
되도록 제네릭을 사용해서 박싱,언박싱을 피해야한다.

참조 https://hongjinhyeon.tistory.com/90

 

[C#] 박싱 과 언박싱 (Boxing & UnBoxing)

일반적인 프로그램을 만들면 이 개념에 대해서 몰라도 개발은 가능합니다.그러나 사용되는 메모리가 많거나 관리가 필요하다면 필수적으로 알아야할 내용입니다. 1.박싱(Boxing) 값타입을 Object

hongjinhyeon.tistory.com

 


작성일: 2022년 12월 9일

'아카이브 > 개발,공부' 카테고리의 다른 글

유니티,깃 협업 환경 세팅 (팀원 공유용)  (0) 2026.05.12
객체지향 프로그래밍  (0) 2026.05.12
Generic  (0) 2026.05.12
상속, Virtual , Abstract, Interface  (0) 2026.05.12
UnityWebRequest 정리  (0) 2026.05.12

클래스의 상속

클래스는 상속을 할 수 있다.
상위 클래스는 부모, 하위 클래스는 자식(child)클래스 라고 부른다.

클래스의 상속은 병렬로는 여러개가 이뤄질 수 없고, 직렬로는 차곡차곡 상속이 가능하다.
자식클래스는 부모클래스의 기능을 모두 사용한다.

//이 클래스는 씬에 놓여있지 않음.
public class ParentTest : MonoBehaviour
{

    private void Awake()
    {
        Debug.Log("부모");
    }
}
//이 클래스는 씬에 놓여짐.
public class playerTest : ParentTest
{
    //아무것도 구현하지않음.
}

Log:
부모

Virtual (가상 키워드)

가상 메소드는 한마디로 자식에게 물려줄 수 있는 메소드다.

위의 상속 부분에 적혀있듯 자식은 부모의 메소드를 자동으로 사용하게 되지만,
때에따라 부모의 메소드를 변형해서 사용하거나, 추가해서 사용하고 싶을 수 있다.

이때 해당 부모 메소드에 Virtual키워드를 적어준다.
그리고 자식에 는 override키워드 같은이름의 메소드를 구현해주면 새롭게 덮어쓰기 해서 구현할 수 있다.

말 그대로 덮어쓰기 이므로, 부모에게 구현되어있던 기능들은 사용할 수 없는데,
base.메소드(); 를 사용하면 부모에 구현되어있던 기능들도 사용할 수 있다.

Virtual 메소드를 덮어쓰기 할지말지는 자식 클래스의 마음이다.
강제성이 없다.

//이 클래스는 씬에 놓여있지 않음.
public class ParentTest : MonoBehaviour
{

    protected virtual void Awake()
    {
        Debug.Log("부모");
    }
}
//이 클래스는 씬에 놓여짐.
public class playerTest : ParentTest
{
    protected override void Awake()
    {
        base.Awake();
        Debug.Log("자식");
    }
}

Log:
부모
자식

Abstract(추상 키워드)

추상은 가상 메소드와 비슷하다.
부모의 Abstract 메소드에는 구현부가 없다. 말그대로 추상적으로 존재만 한다.

대신 자식이 override 해서 구현을 해줘야한다.
부모에게 추상 메서드가 있다면 자식은 강제로 구현해야한다.

활용
아래처럼 자식 클래스들에게 공통적인 정의를 내려주고, 자식들은 각각 다르게 부모가 내려준 정의를 다르게 구현한다.

//이 클래스는 씬에 놓여있지 않음.
public abstract class ParentTest : MonoBehaviour
{

    protected virtual void Awake()
    {
        Debug.Log("부모");
        Habit();
    }

    protected abstract void Habit();
}
//이 클래스는 씬에 놓여짐.
public class playerTest : ParentTest
{
    protected override void Awake()
    {
        base.Awake();
        Debug.Log("자식");
    }

    protected override void Habit()
    {
        Debug.Log("다리떨기");
    }
}

Log:
부모
다리떨기
자식

Interface

언뜻보면 상속과 비슷하다. 변수를 사용할 수 없고, 메서드에대한 정의나 프로퍼티만 가질 수 있다.
클래스상속과 다르게 인터페이스는 병렬로 여러개 상속받을 수 있다.

abstract와 마찬가지로 인터페이스에 정의된 메소드는 무조건 상속받은 클래스에서 구현해줘야한다.

//인터페이스는 파일이름 맨 앞에 I 붙임
public interface ITest
{    
    public void SaChoonGi();
}
//이 클래스는 씬에 놓여짐.
public class playerTest : ParentTest , ITest
{   
    protected override void Awake()
    {
        base.Awake();
        Debug.Log("자식");
        SaChoonGi();
    }

    protected override void Habit()
    {
        Debug.Log("다리떨기");
    }

    public void SaChoonGi()
    {
        Debug.Log("반항");
    }


}

Log:
부모
다리떨기
자식
반항

정리

-Vritual은 하나의 기능을 하는 완전한 클래스이며, 자식클래스에서 상속해서 추가적인 기능추가 및 virtual 한정자가 달린 것을 재정의해서 사용가능하다.

-Abstract는 여러개의 자식 클래스에서 공유할 기본 클래스의 공통적인 정의만 하고 ,자식클래스에서 abstract 한정자가 달린 것을 모두 재정의(필수)해야 한다.

-Interface에서도 abstract와 비슷하지만 멤버변수를 사용할 수 없다.

  • 보통 abstract는 개념적으로 계층적인 구조에서 사용이 된다.
    ex)동물(움직이기)/포유류(새끼 낳기)/강아지
  • Interface는 서로다른 계층이나 타입이라도 같은기능을 추가하고 싶을때 사용합니다.
    로봇(말하기) , 사람(말하기) , 지나가는 비둘기(말하기)

참조 https://hongjinhyeon.tistory.com/93

 

[C#] Virtual(가상) vs Abstract(추상) vs Interface(인터페이스)

OOP개념에서 상속을 이야기할 때 Virtual이나 Abstract 한정자가 사용이됩니다.두개를 사용하면서 헷갈리는 점을 정리했습니다. 또한 Interface와 비슷한 기능을 제공하는데같이 비교해보겠습니다. 1.Vi

hongjinhyeon.tistory.com

 


작성일 2022년 12월 5일

 

'아카이브 > 개발,공부' 카테고리의 다른 글

유니티,깃 협업 환경 세팅 (팀원 공유용)  (0) 2026.05.12
객체지향 프로그래밍  (0) 2026.05.12
Generic  (0) 2026.05.12
값 타입, 참조 타입 / Boxing, UnBoxing  (0) 2026.05.12
UnityWebRequest 정리  (0) 2026.05.12

+ Recent posts