객체지향 코드인 C#의 핵심인 class를 드디어 오늘 정리하고자한다.
→ 현실 세계의 객체들을 프로그래밍에 반영하여 데이터(속성)와 기능(행동)을 하나의 단위로 묶어 개발하는 프로그래밍 패러다임이다.
| 특징 | 설명 |
|---|---|
| 캡슐화 | 관련 있는 데이터와 메서드를 묶고 외부에 숨김 |
| 상속 | 기존 클래스의 기능을 새로운 클래스에 물려줌 |
| 다형성 | 같은 이름의 메서드가 다양한 방식으로 동작 |
| 추상화 | 불필요한 정보는 숨기고 핵심만 표현 |
public class Car
{
public string Model;
public void Drive()
{
Console.WriteLine($"{Model}이(가) 달립니다.");
}
}
static void Main(string[] args)
{
// main 함수에서 사용 코드
Car myCar = new Car(); // new 키워드로 생성자 사용
myCar.Model = "소나타"; // 내부의 외부 공개 가능한 값에 접근 및 값 할당
myCar.Drive(); // 내부의 외부 공개 가능한 함수 기능 사용
}
소나타이(가) 달립니다.
생성자는 클래스나 구조체의 인스턴스를 생성할 때 자동으로 호출되는 특수한 메서드이다.
생성자는 객체의 초기화는 생성자의 가정 핵심 역할이다. 필드나 속성을 객체가 생성될 때 원하는 초기값으로 생성 할 수 있다.
class Player
{
public string Name;
public int HP;
public Player(string name)
{
Name = name;
HP = 100; // 기본 체력
}
}
영어적 인스턴스(Instance): 어떤 것의 구체적인 예, 사례
인스턴스화를 했다 > 실체화된 사례, 구체적인 예시
프르래밍적 인스턴스(Instance): 클래스로(설계도)부터 만들어진 하나의 구체적인 객체(실체)
플레이어 이름 초기화, 기본 체력 설정, 플레이어 초기화 문구 출력 등을 넣어서 객체가 생성될 때 필요한 작업을 수행 할 수 있다.
class Player
{
public string Name;
public int HP;
public Player(string name)
{
Name = name;
HP = 100; // 기본 체력
Console.WriteLine("플레이어 초기화됨!"); // 로직 수행!!
}
}
동일한 이름으로 매개변수만 다른 생성자 여러 개를 만들 수 있다. 사용자가 상황에 따라 적절한 생성자를 선택하여 사용가능하다.
class Player
{
public string Name;
public int HP;
public Player()
{
Name = "None"; // 기본 이름
HP = 100; // 기본 체력
InitPrint(); // 출력
}
public Player(string name)
{
Name = name;
HP = 100; // 기본 체력
InitPrint();
}
public Player(string name, int hp)
{
Name = name;
HP = hp;
InitPrint();
}
private void InitPrint()
{
Console.WriteLine("플레이어 초기화됨!");
Console.WriteLine($"플레이어 이름: {Name}");
Console.WriteLine($"플레이어 이름: {HP}");
}
}
자식 클래스가 생성될 때, 먼저 부모 클래스의 생성자가 호출되어 초기화 된다. (생성자 실행순서 : 부모 > 자식 > 손자)
base() 키워드로 명시적으로 호출할 수도 있다.
class Animal
{
public Animal(string name)
{
Console.WriteLine($"{name} 동물이 생성됨!");
}
}
class Dog : Animal
{
public Dog() : base("강아지") { }
}
이전에 상수관련해서 정리 하지 않았는가? 그것을 활용하여 생성자를 활용하는 것. 생성자를 통해서 필드를 설정하고, 이후에는 수정하지 못하도록 만드는 패턴이다.(상수 관련해서 봤으면 또 보는 이야기 일 것이다)
class ImmutableItem
{
public readonly int Id;
public readonly string Name;
public ImmutableItem(int id, string name)
{
Id = id;
Name = name;
}
}
| 역할 | 설명 |
|---|---|
| 객체 초기화 | 필드나 속성을 원하는 값으로 설정 |
| 초기화 로직 수행 | 생성 시점에 필요한 작업 수행 (ex. 로깅, 파일열기 등) |
| 생성자 오버로딩 | 다양한 초기 상태를 만들 수 있도록 여러 생성자 제공 |
| 상속 시 부모 초기화 | 자식 생성자에서 부모 생성자 호출하여 초기화 수행 |
| 불변 객체 생성 | readonly 필드를 생성자에서만 설정하여 외부 변경 차단 |
상속? 뉴스 같은 곳에서 가끔 들려오는 단어이다. 기존의 제산 같은 것들을 자식에게 물려주기 위한 행동을 뜻한다. 프로그래밍에서도 비슷하다.
상속은 기존 클래스(부모, 상위 클래스, 기반 클래스) 의 필드, 메서드, 속성 등을 새로운 클래스(자식, 부 클래스, 파생 클래스) 가 물려받는 기능이다.
이렇게 물려줘, 클래스 간의 종속 관계를 만듦으로서 객체를 조직화 할 수 있다.
상속의 핵심은 재사용과 확장의 기반이라고 할 수 있다.
클래스를 만들다 보면, 공통 구조는 같지만, 구체적인 동작은 자식 클래스가 구현해야 할 때 사용한다. 이때 사용하는 것이 추상 클래스다.
| 항목 | 추상 클래스 | 일반 클래스 |
|---|---|---|
| 인스턴스 생성 | 불가 | 가능 |
| 추상 메서드 정의 | 가능 | 불가 |
| 일부 구현 포함 | 가능 | 가능 |
| 목적 | 공통 + 확장 | 구체적 구현 |
abstract class Animal
{
public string Name { get; set; }
public Animal(string name)
{
Name = name;
}
public void Sleep()
{
Console.WriteLine($"{Name}이(가) 잠을 잡니다.");
}
// 추상 메서드: 자식 클래스가 반드시 구현해야 함
public abstract void MakeSound();
}
class Dog : Animal
{
public Dog(string name) : base(name) {}
// 반드시 구현
public override void MakeSound()
{
Console.WriteLine($"{Name}이(가) 멍멍!");
}
}
class Cat : Animal
{
public Cat(string name) : base(name) {}
public override void MakeSound()
{
Console.WriteLine($"{Name}이(가) 야옹!");
}
}
static void Main(string[] args)
{
Animal dog = new Dog("초코");
dog.Sleep();
dog.MakeSound();
Animal cat = new Cat("나비");
cat.Sleep();
cat.MakeSound();
}
초코이(가) 잠을 잡니다.
초코이(가) 멍멍!
나비이(가) 잠을 잡니다.
나비이(가) 야옹!
| 항목 | 설명 |
|---|---|
| 목적 | 공통 로직 제공 + 자식이 핵심 구현 |
| 생성 가능 여부 | ❌ 직접 객체 생성 불가 |
| 추상 메서드 | 자식 클래스에서 반드시 override |
| 일반 메서드 | 가능 |
| 인터페이스와 차이 | 필드/생성자/기본 구현 가능 |
| 키워드 | 개념 설명 |
|---|---|
: | 상속을 나타냄 (class Child : Parent) |
base | 부모 클래스의 생성자나 메서드를 호출할 때 사용 |
virtual | 부모 클래스에서 메서드 오버라이딩을 허용함 |
override | 자식 클래스에서 부모의 virtual 메서드를 재정의할 때 사용 |
sealed | 이 클래스를 더 이상 상속할 수 없도록 제한 |
abstract | 추상 클래스/메서드 정의 (구현 없이 선언만 가능) |
new | 부모 클래스의 동일한 이름의 멤버를 숨기고 새로 정의할 때 사용 |
class Unit
{
public readonly string Name = "None";
public Unit(string name)
{
Name = name;
}
}
class Character : Unit // 단일 상속
{
public Character(string name) : base(name) {} // 이런 식으로 부모(Unit)의 생성자 호출
public virtual void Attack()
{
Console.WriteLine($"{Name}이(가) 기본 공격!");
}
}
// class Warrior : Character, Unit // 이렇게 작성시 Error
sealed class Warrior : Character // 단일 상속, sealed로 더 이상 상속 안함을 명시
{
public Warrior(string name) : base(name) {} // 이런 식으로 부모(Character)의 생성자 호출
// 가상으로 만들어져 있던 함수를, 오버라이드 해서 재정의하여 사용.
public override void Attack()
{
Console.WriteLine($"{Name}이(가) 검으로 공격!");
}
}
C#에서 class와 struct는 객체를 정의할 수 있는 두 가지 방법이다. 이 둘은 사용하는 방식이 유사하지만, 메모리 처리 방식과 구조적으로 몇 가지 중요한 차이점이 있다.
| 구분 | class | struct |
|---|---|---|
| 메모리 위치 | 힙(Heap) | 스택(Stack) |
| 타입 | 참조 타입 (Reference Type) | 값 타입 (Value Type) |
| 기본 생성자 | 제공됨 (사용 가능) | 제공되지만 사용 불가 (사용자 정의만 가능) |
| 상속 가능 여부 | O (다형성 구현 가능) | X (인터페이스 구현만 가능) |
| null 허용 여부 | 가능 | 불가능 (nullable로 가능: int?) |
| GC(가비지 컬렉터) | 대상이 됨 | 대상이 아님 |
| 값 복사 방식 | 참조 전달 (주소 복사) | 값 복사 (전체 복사) |
| 용도 | 복잡하고 크기가 큰 객체에 적합 | 작고, 변하지 않는 데이터에 적합 |
| 기본 생성자 호출 | 가능 | 컴파일러가 기본 생성자 자동 생성 |
게임 캐릭터나 아이템, NPC 같이 많은 데이터와 복잡한 동작들은 class
상속, 추상화, 다형성 필요는 무조건 class
단순한 좌표, 크기, RGB(색상) 등 간단하고 빠르게 처리가 필요한 것들 struct
성능 최적화가 중요할 때(단, 너무 큰 구조체는 오히려 성능 저하가 발생) struct