⭐ 클래스란?
객체 지향 프로그래밍의 핵심 개념으로, 객체를 만들기 위해 변수와 메서드로 정의한 사용자 정의 데이터형입니다.
현실세계의 사물과 개념을 바탕으로 설계해나가는 객체 지향 프로그래밍에서 아주 중요한 개념입니다.
클래스는 객체를 생성하기 위해 데이터와 메서드를 이용해 이루어진 하나의 설계도라고 생각하시면 됩니다.
클래스라는 설계도를 바탕으로 실체를 생성하게 되는데 여기서 만들어진 실체를 객체, 즉 인스턴스라고 합니다.
그리고 클래스를 통해 객체를 생성하는 과정을 인스턴스화 라고 부릅니다.
⭐ 클래스의 주요 구성 요소 (멤버)
클래스는 여러 종류의 멤버들로 구성되어 있습니다.아래에서 자주 사용되는 멤버들에 대해 알아보겠습니다.
● 필드 (Fields)
○ 클래스 내부에 선언되는 변수입니다. 객체의 상태나 데이터를 저장하는 역할을 합니다.
● 상수 (Const)
○ 선언과 동시에 값을 할당해야 하며, 한 번 할당된 상수는 값이 변하지 않습니다.
● 프로퍼티 (Property)
○ 필드에 직접 접근하는 대신, 데이터를 읽거나 쓸 수 있도록 제어하는 속성입니다.
get, set 접근자를 사용해 데이터의 유효성을 검사하거나 특정 로직을 수행할 수 있습니다.
● 메서드 (Method)
○ 클래스 내부에 선언되는 함수입니다. 객체가 수행할 기능을 정의합니다.
가능하면 하나의 함수는 하나의 기능만을 수행하도록 합니다.
● 생성자 (Constructor)
○ 클래스의 인스턴스를 생성할 때 호출되는 특별한 메서드입니다. 클래스의 이름과 같으며 주로 초기화 작업을 수행합니다.
● 소멸자 (Destructor)
○ 생성자와 반대대는 소멸자라는 개념도 있지만, C#에서는 대부분의 상황에 따로 소멸자를 직접 제어하지 않습니다.
● 이벤트 (Event)
○ 특정 상황이 발생했을 때 다른 코드에 알리는 메커니즘입니다.
● 인덱서 (Indexer)
○ 클래스의 인스턴스를 배열처럼 인덱스를 사용하여 접근할 수 있도록 합니다.
⭐ 클래스 사용
클래스 선언
클래스를 선언하는 방법은 다음과 같습니다.
//클래스 선언 형식
접근제한자 class 클래스명
{
}
//클래스 선언 예제
public class GameManager
{
}
접근제한자
클래스 멤버에 대한 접근 권한을 제어하는 키워드입니다.
● private: 클래스 내부에서만 접근 가능
● protected: 클래스 내부와 상속받은 자식 클래스만 접근 가능
● public: 클래스 외부에서도 접근 가능
클래스 객체 만들기
선언한 클래스를 객체로 인스턴스화 하는 방법입니다.
생성된 객체의 데이터 정보는 Heap 메모리에 할당되며 해당 데이터 정보를 참조할 수 있는 주소 정보가 Stack 메모리에 저장됩니다.
//클래스 객체 생성
클래스타입 객체명 = new 클래스타입();
//클래스 객체 생성 예제
GameManager gameManager = new GameManager();
⭐ 생성자와 소멸자
생성자와 소멸자는 각각 클래스가 생성될 때, 클래스가 소멸할 때 호출되는 함수입니다.
클래스 객체를 생성할 때 호출되며, 데이터를 초기화할 때 자주 사용됩니다.
또한 오버로딩을 이용해 여러 개의 생성자를 정의하는 방법도 있습니다.
반대로 소멸자는 생성된 객체가 소멸할 때 호출되는 함수로 C#에서는 일반적으로 소멸자를 따로 정의하지 않습니다.
그 이유는 C#에서는 GC라고 부르는 가비지 컬렉터가 힙 메모리에서 할당되었지만 사용하지 않는 메모리 공간을 자동으로 정리하기 때문에 명시적으로 소멸자를 호출하는 경우가 거의 없습니다.
생성자 정의
생성자를 정의하는 기본 형식은 다음과 같습니다.
여기서 생성자의 이름은 클래스 이름과 같아야합니다 (대소문자도 같아야 됨)
//생성자 정의 형식
접근제한자 클래스명()
{
실행 내용
}
위의 형식을 따라서 플레이어의 이름과 레벨을 생성자로 초기화하는 기본적인 생성자 예제를 만들어 보겠습니다.
아래 예제에서는 분명 두 개의 생성자를 정의했지만 오버로딩을 사용해 아무 문제없습니다.
객체를 생성할 때 new Player() 안에 인자값이 없으면 1번 생성자를, string, int 인자값을 넣어주면 2번 생성자를 호출하게 됩니다.
//생성자 예제
public class Player
{
private string playerName;
private int playerLevel;
//1번 생성자
public Player() -> Player player = new Player()시 호출
{
Console.WriteLine("매개변수가 없는 생성자");
}
//2번 생성자
public Player(string playerName, int playerLevel) -> Player player = new Player(string, int)시 호출
{
this.playerName = playerName;
this.playerLevel = playerLevel;
Console.WriteLine($"{playerName}님의 레벨은 {playerLevel}입니다.");
}
}
소멸자 정의
C#에서는 소멸자를 따로 정의할 필요가 없지만 기본 개념만 살펴보겠습니다.
먼저 소멸자는 클래스 이름 앞에 ~를 붙여서 정의하고 반환형과 매개변수가 없습니다.
소멸자의 호출 시점은 객체가 메모리에서 해제될 때 사용되지만 C#은 GC(가비지 컬렉터)를 통해 자동으로 관리되며 소멸되는 시점이 명확하지 않기 때문에 직접 소멸자를 호출하는 방법은 추천하지 않습니다.
//소멸자 기본 형식
~클래스명()
{
}
//소멸자 정의 예제
~Player()
{
Console.WriteLine("객체 제거됨");
}
⭐ 상속과 다형성
상속
기존 클래스의 속성과 메서드를 물려받아 새로운 클래스를 만드는 것을 뜻합니다.
여기서 기존 클래스는 부모 클래스, 새로운 클래스는 자식 클래스를 말합니다.
public class Animal
{
protected string name;
public int age;
private float weight;
}
public class Dog : Animal
{
public void Init()
{
name = "강아지"; //protected -> 접근 가능
age = 5; //public -> 접근 가능
weight = 3.6f; //private -> 자식 클래스라도 접근 불가능
}
}
만약 부모 클래스에 인자값을 받는 생성자가 있는 경우 자식 클래스에서도 같은 형식의 생성자를 정의해야합니다.
public class Animal
{
protected string name;
public int age;
public Animal(string name, int age)
{
this.name = name;
this.age = age;
}
}
public class Dog : Animal
{
//Dog 객체를 생성할 때 받은 인자를 base 키워드를 통해 부모 생성자에게 전달
public Dog(string name, int age) : base(name, age) { }
}
this / base 키워드
this와 base 키워드는 동일한 이름의 멤버가 있을 경우에 사용됩니다.
this 키워드는 현재 클래스를 지칭하는 키워드입니다.
예를 들어 변수를 초기화할 때 현재 클래스의 변수와 초기화를 위해 받아온 매개변수 명이 같은 경우 구별하기 위해 사용할 수 있습니다.
public class Animal
{
protected string name;
public int age;
public void Init(string name, int age)
{
this.name = name; //this.name = 내 클래스에서 선언한 변수 멤버 / name = Init 메서드의 매개변수
this.age = age; //this.age = 내 클래스에서 선언한 변수 멤버 / age = Init 메서드의 매개변수
}
}
base 키워드는 상속관계에서 부모 클래스를 지칭하는 키워드입니다.
예를 들어 부모와 자식 클래스 모두 같은 이름의 메서드가 있을 경우 base 키워드를 통해 부모 클래스의 메서드임을 지칭할 수 있습니다.
public class Animal
{
protected string name = 두부;
public int age = 3;
public virtual void Init() -> 가상함수
{
Console.WriteLine("부모 클래스");
}
}
public class Dog : Animal
{
public override void Init() -> 오버라이드
{
base.Init(); -> 부모 클래스 Init() 함수 호출
Console.WriteLine($"이름: {name}" +
$"\n나이: {age}");
}
}
출력결과
-> 부모 클래스
이름: 두부
나이: 3
다형성
하나의 타입(클래스 또는 인터페이스 등)이 여러 형태로 동작하는 것을 뜻합니다.
쉽게 말해 같은 이름의 타입이 상황(객체)에 따라 다른 동작을 할 수 있도록 하는 것입니다.
다형성의 장점
● 코드를 재활용할 수 있다.
● 구조를 간결하게 할 수 있다.
● 추후 확장에 용이해진다.
다형성을 구현하는 대표적인 기법으로는 오버라이딩, 오버로딩 그리고 인터페이스 등이 있습니다.
오버라이딩 (Overriding)
오버라이딩은 부모 클래스의 메서드를 자식 클래스에서 재정의하여, 실제 객체에 따라 다른 동작을 하도록 하는 것입니다.
아래 예제의 Unit 클래스는 추상 클래스로 Attack()과 GetDamage()라는 추상 메서드를 선언하였습니다.
부모 클래스에서 선언한 추상 메서드는 반드시 선언만 할 수 있고, 추가적인 구현은 자식 클래스에서 재정의를 통해 할 수 있습니다.
< 추상 클래스를 이용한 오버라이딩 >
public abstract class Unit
{
public abstract void Attack();
public abstract void GetDamage();
}
public class Soldier : Unit
{
public override void Attack()
{
Console.WriteLine("공격!");
}
public override void GetDamage()
{
Console.WriteLine("대미지를 입었습니다.");
}
}
이번엔 가상 함수를 이용한 오버라이딩 예제입니다.
부모 클래스에서 virtual 키워드를 이용하여 가상 함수를 구현합니다. 여기서 추상 메서드와 다른 점은 부모 클래스에서 직접 구현도 할 수 있다는 것입니다.
자식 클래스에서는 마찬가지로 override 키워드를 통해 재정의 할 수 있으며 base 키워드를 통해 부모 클래스의 기능과 함께 추가적인 구현을 할 수 있습니다.
< 가상함수를 이용한 오버라이딩>
public class Unit
{
public virtual void Attack()
{
Console.WriteLine("부모 클래스에서 공격!");
}
public virtual void GetDamage()
{
Console.WriteLine("부모 클래스가 대미지를 입었습니다!");
}
}
public class Soldier : Unit
{
public override void Attack()
{
base.Attack();
Console.WriteLine("자식 클래스에서 공격!");
}
public override void GetDamage()
{
base.GetDamage();
Console.WriteLine("자식 클래스가 대미지를 입었습니다.");
}
}
이렇게 오버라이딩을 이용하면 부모 클래스에서 정의한 큰 틀의 메서드들을 여러 자식 클래스에서 각각 다른 기능으로 구현할 수 있다는 장점이 있습니다.
오버로딩 (Overloading)
오버로딩은 매개변수의 개수나 타입이 다르더라도 같은 이름의 메서드를 여러 개 정의할 수 있는 것을 말합니다.
컴파일러는 호출 시점에 인자 개수나 타입을 통해 어떤 메서드를 실행할지 결정합니다.
public class Overloading
{
public void Print(int a)
{
Console.WriteLine($"정수 출력: {a}");
}
public void Print(string str)
{
Console.WriteLine($"문자열 출력: {str}");
}
public void Print(int a, int b)
{
Console.WriteLine($"두 정수의 합 출력: {a + b}");
}
}
internal class Program
{
static void Main(string[] args)
{
Overloading overloading = new Overloading();
//각 메서드 출력
overloading.Print(1); // 1
overloading.Print("안녕"); // 안녕
overloading.Print(2, 5); // 7
}
}
⭐ 정적 (static) 멤버
정적 멤버는 객체와 상관없이 클래스 자체에 속하는 멤버를 의미합니다. 쉽게 말해 인스턴스를 만들지 않아도 접근할 수 있는 변수나 메서드입니다.
정적 멤버는 클래스당 오직 하나만 존재하며 클래스명.멤버로 바로 접근이 가능합니다.
주로 전역적으로 공유되는 데이터(게임매니저, 유틸 관련 클래스)를 구현할 때 유용합니다.
정적 멤버를 활용하는 디자인 패턴인 싱글턴 패턴을 활용하여 클래스의 인스턴스를 단 하나만 생성하도록 보장할 수 있습니다.
싱글턴 패턴에 대해서는 따로 작성해둔 글이 있으니 궁금하신 분들은 넘어가서 싱글턴 패턴에 대해서 알아보시는 것을 추천드립니다.
싱글턴 패턴 알아보기
'개발, IT > C#' 카테고리의 다른 글
| [유니티 / C#] static (정적) 한정자 (0) | 2025.09.17 |
|---|---|
| [유니티 / C#] 메모리와 가비지 컬렉터 (0) | 2025.09.17 |
| [유니티 / C#] 프로퍼티 (Property) (0) | 2025.09.15 |
| [유니티 / C#] 객체 지향 프로그래밍과 SOLID 원칙 (0) | 2025.09.15 |
| [유니티 / C#] 구조체, 구조체 클래스 차이점 (0) | 2025.09.12 |