
상속(Inheritance)은 이름 그대로, 부모 클래스의 속성(필드, 프로퍼티)과
기능(메서드)을 자식 클래스가 물려받아 사용할 수 있게 만드는 기능입니다.
이렇게 하면 자식 클래스들은 자신만의 고유한 기능을 추가만 하면 됩니다.
코드의 재사용성이 극대화되고, 유지보수가 매우 편리해지죠.
상속을 설명할 때 C#에서 사용하는 공식 용어를 알아볼까요?
기반 클래스(Base Class / 부모 클래스 / 슈퍼 클래스): 유산을 물려주는 클래스
파생 클래스(Derived Class / 자식 클래스 / 서브 클래스): 유산을 물려받는 클래스
[문법]
:(콜론)을 사용해서 부모와 자식 간의 상속 관계를 표시합니다.
C#에서 클래스는 하나의 클래스만 상속받을 수 있습니다.
대신, 인터페이스를 사용하면 다중 상속을 흉내 낼 수 있습니다.
접근 제한자 class 부모클래스
{
// 공통 멤버
}
접근 제한자 class 자식클래스 : 부모클래스
{
// 확장된 멤버
}
[코드]
using System;
// 부모 클래스
class Person
{
public string Name { get; set; }
public int Age { get; set; }
public void Introduce()
{
Console.WriteLine($"안녕하세요, 저는 {Name}이고 {Age}살입니다.");
}
}
// 자식 클래스 1
class Student : Person
{
public string School { get; set; }
public void Study()
{
Console.WriteLine($"{Name} 학생은 {School}에서 공부 중입니다.");
}
}
// 자식 클래스 2
class Teacher : Person
{
public string Subject { get; set; }
public void Teach()
{
Console.WriteLine($"{Name} 선생님은 {Subject}을 가르칩니다.");
}
}
class Program
{
static void Main()
{
Student student = new Student
{
Name = "홍길동",
Age = 18,
School = "서울고등학교"
};
student.Introduce(); // 부모 클래스의 메서드
student.Study(); // 자식 클래스의 메서드
Teacher teacher = new Teacher
{
Name = "김철수",
Age = 30,
Subject = "수학"
};
teacher.Introduce(); // 부모 메서드
teacher.Teach(); // 자식 메서드
}
}
[실행 결과]
안녕하세요, 저는 홍길동이고 18살입니다.
홍길동 학생은 서울고등학교에서 공부 중입니다.
안녕하세요, 저는 김철수이고 30살입니다.
김철수 선생님은 수학을 가르칩니다.
자식 클래스가 부모의 능력을 빌려와야 할 때가 있습니다.
바로 이 순간, '부모님 찬스'처럼 사용할 수 있는 키워드가 base입니다.
base는 부모 클래스의 생성자나 메서드에 접근할 때 사용합니다.
부모 클래스의 메서드를 자식 클래스에서 재정의(override)할 때가 있습니다.
단순히 덮어쓰는 게 아니라, 부모 클래스가 하던 일을 수행한 뒤에
나만의 기능을 추가하고 싶을 수 있죠.
[코드]
using System;
// 부모 클래스: Animal
class Animal
{
public virtual void MakeSound()
{
Console.WriteLine("동물이 소리를 냅니다.");
}
}
// 자식 클래스: Dog
class Dog : Animal
{
public override void MakeSound()
{
// 1. 강아지만의 특별한 기능
Console.WriteLine("멍멍!");
// 2. base 키워드로 부모 메서드 호출
base.MakeSound();
}
}
class Cat : Animal
{
public override void MakeSound()
{
// 1. 고양이만의 특별한 기능
Console.WriteLine("야옹!");
// 2. base 키워드로 부모 메서드 호출
base.MakeSound();
}
}
class Program
{
static void Main()
{
Dog myDog = new Dog();
myDog.MakeSound();
Cat myCat = new Cat();
myCat.MakeSound();
}
}
[실행 결과]
멍멍!
동물이 소리를 냅니다.
야옹!
동물이 소리를 냅니다.
집을 지을 때 1층을 다 짓고 2층을 올리는 것처럼, 자식 클래스의 객체를
만들기 전에는 반드시 부모 클래스의 객체가 먼저 생성되어야 합니다.
[코드]
using System;
// 부모 클래스: Person
class Person
{
public string Name { get; set; }
public int Age { get; set; }
// 이름과 나이를 받는 생성자
public Person(string name, int age)
{
this.Name = name;
this.Age = age;
Console.WriteLine($"[Person 생성] 이름: {this.Name}, 나이: {this.Age}");
}
}
// 자식 클래스: Employee
class Employee : Person
{
public string EmployeeId { get; set; }
// 이름, 나이, 사원 ID를 받는 생성자
// ': base(name, age)'를 주목하세요!
public Employee(string name, int age, string employeeId) : base(name, age)
{
this.EmployeeId = employeeId;
Console.WriteLine($"[Employee 생성] 사원 ID: {this.EmployeeId}");
}
}
class Program
{
static void Main()
{
Employee newEmployee = new Employee("김철수", 30, "E12345");
}
}
[실행 결과]
[Person 생성] 이름: 김철수, 나이: 30
[Employee 생성] 사원 ID: E12345
[실행 과정]
1. Employee생성자는 name, age, employeeId 3개의 값을 받습니다.
2. name과 age는 부모인 Person이 초기화해야 합니다.
3. base(name, age)를 통해 부모 생성자에게 전달합니다.
4. 부모인 Person의 생성자가 먼저 실행되어 이름과 나이를 설정합니다
5. 그 후 Employee생성자의 코드가 실행되어 사원 ID를 설정합니다.
6. 실행 결과를 보면 Person생성자가 먼저 호출된 것을 확인할 수 있습니다.
C#에서는 항상 부모 생성자가 먼저, 자식 생성자가 나중에 실행됩니다.
base(...)는 이 과정에서 어떤 부모 생성자를 호출할지,
어떤 값을 넘길지를 우리가 직접 지정하는 역할을 합니다.
이제 여러분이 직접 RPG 게임을 개발한다고 상상해 봅시다.
기사(Knight), 마법사(Wizard) 클래스를 직접 만들어야 합니다.
해당 클래스에서는 모두 '이름', 'HP' 같은 공통된 속성을 가지고 있어요.
공통된 코드들을 모든 클래스에 복사-붙여넣기 해야 할까요?
이럴 때, 상속을 사용하면 기능을 물려받아 재사용할 수 있습니다.
상속을 배울 때 함께 알아두면 좋은 접근 제한자는 protected가 있습니다.
protected는 상속과 깊은 관련이 있어요. 외부에서는 접근할 수 없지만,
나(부모 클래스)와 나를 상속받은 자식 클래스에서는 자유롭게 접근할 수 있어요.
모든 캐릭터의 공통점을 담은 Character클래스를 부모 클래스로 만듭니다.
[코드]
// 부모 클래스 (Base Class)
public class Character
{
// 자식 클래스에서 접근할 수 있도록 protected를 사용합니다.
protected string _name;
protected int _hp;
// 실무에서는 프로퍼티를 통해 private 필드 + protected 프로퍼티/메서드를 사용합니다.
public int Level { get; protected set; } // 프로퍼티도 상속됩니다.
// 부모 클래스의 생성자
public Character(string name, int hp)
{
_name = name;
_hp = hp;
Level = 1;
Console.WriteLine("Character 생성자 호출!");
}
public void Move()
{
Console.WriteLine($"{_name}(이)가 이동합니다.");
}
public void ShowInfo()
{
Console.WriteLine($"이름: {_name} | HP: {_hp} | 레벨: {Level}");
}
}
이제 Knight와 Wizard클래스를 자식 클래스로 만들어 보겠습니다.
[코드]
// 자식 클래스 1 (Derived Class)
public class Knight : Character // Character 클래스를 상속받습니다.
{
private int _physicalPower; // Knight만의 고유한 필드
// 자식 클래스의 생성자
public Knight(string name, int hp, int physicalPower) : base(name, hp)
{
_physicalPower = physicalPower;
Console.WriteLine("Knight 생성자 호출!");
}
// Knight만의 고유한 메서드
public void Charge()
{
Console.WriteLine($"{_name}(이)가 {_physicalPower}의 힘으로 돌진합니다!");
}
}
// 자식 클래스 2 (Derived Class)
public class Wizard : Character // Character 클래스를 상속받습니다.
{
private int _magicalPower; // Wizard만의 고유한 필드
public Wizard(string name, int hp, int magicalPower) : base(name, hp)
{
_magicalPower = magicalPower;
Console.WriteLine("Wizard 생성자 호출!");
}
public void CastMagic()
{
Console.WriteLine($"{_name}(이)가 {_magicalPower}의 마력으로 마법을 시전합니다!");
}
}
Knight와 Wizard객체는 부모에게 물려받은 기능과
자신만의 고유한 기능을 모두 사용할 수 있습니다.
[코드]
class Program
{
static void Main()
{
Knight arthur = new Knight("아서", 150, 90);
Wizard erin = new Wizard("에린", 100, 200);
Console.WriteLine("\n--- 기사 정보 ---");
arthur.ShowInfo(); // 부모(Character)에게 물려받은 메서드
arthur.Move(); // 부모(Character)에게 물려받은 메서드
arthur.Charge(); // 자신(Knight)만의 고유한 메서드
Console.WriteLine("\n--- 마법사 정보 ---");
erin.ShowInfo(); // 부모(Character)에게 물려받은 메서드
erin.Move(); // 부모(Character)에게 물려받은 메서드
erin.CastMagic(); // 자신(Wizard)만의 고유한 메서드
}
}
using System;
// 부모 클래스 (Base Class)
public class Character
{
// 자식 클래스에서 접근할 수 있도록 protected를 사용합니다.
protected string _name;
protected int _hp;
// 실무에서는 프로퍼티를 통해 private 필드 + protected 프로퍼티/메서드를 사용합니다.
public int Level { get; protected set; } // 프로퍼티도 상속됩니다.
// 부모 클래스의 생성자
public Character(string name, int hp)
{
_name = name;
_hp = hp;
Level = 1;
Console.WriteLine("Character 생성자 호출!");
}
public void Move()
{
Console.WriteLine($"{_name}(이)가 이동합니다.");
}
public void ShowInfo()
{
Console.WriteLine($"이름: {_name} | HP: {_hp} | 레벨: {Level}");
}
}
// 자식 클래스 1 (Derived Class)
public class Knight : Character // Character 클래스를 상속받습니다.
{
private int _physicalPower; // Knight만의 고유한 필드
// 자식 클래스의 생성자
public Knight(string name, int hp, int physicalPower) : base(name, hp)
{
_physicalPower = physicalPower;
Console.WriteLine("Knight 생성자 호출!");
}
// Knight만의 고유한 메서드
public void Charge()
{
Console.WriteLine($"{_name}(이)가 {_physicalPower}의 힘으로 돌진합니다!");
}
}
// 자식 클래스 2 (Derived Class)
public class Wizard : Character // Character 클래스를 상속받습니다.
{
private int _magicalPower; // Wizard만의 고유한 필드
public Wizard(string name, int hp, int magicalPower) : base(name, hp)
{
_magicalPower = magicalPower;
Console.WriteLine("Wizard 생성자 호출!");
}
public void CastMagic()
{
Console.WriteLine($"{_name}(이)가 {_magicalPower}의 마력으로 마법을 시전합니다!");
}
}
class Program
{
static void Main()
{
Knight arthur = new Knight("아서", 150, 90);
Wizard erin = new Wizard("에린", 100, 200);
Console.WriteLine("\n--- 기사 정보 ---");
arthur.ShowInfo(); // 부모(Character)에게 물려받은 메서드
arthur.Move(); // 부모(Character)에게 물려받은 메서드
arthur.Charge(); // 자신(Knight)만의 고유한 메서드
Console.WriteLine("\n--- 마법사 정보 ---");
erin.ShowInfo(); // 부모(Character)에게 물려받은 메서드
erin.Move(); // 부모(Character)에게 물려받은 메서드
erin.CastMagic(); // 자신(Wizard)만의 고유한 메서드
}
}
[실행 결과]
Character 생성자 호출!
Knight 생성자 호출!
Character 생성자 호출!
Wizard 생성자 호출!
--- 기사 정보 ---
이름: 아서 | HP: 150 | 레벨: 1
아서(이)가 이동합니다.
아서(이)가 90의 힘으로 돌진합니다!
--- 마법사 정보 ---
이름: 에린 | HP: 100 | 레벨: 1
에린(이)가 이동합니다.
에린(이)가 200의 마력으로 마법을 시전합니다!
Knight와 Wizard클래스에서 Move()나 ShowInfo()메서드를 정의하지 않았지만,
부모인 Character로부터 물려받았기 때문에 아무런 문제 없이 사용할 수 있습니다.
상속은 "A는 B이다! (is-a)" 관계일 때 사용하는 것이 좋습니다.
예: "Knight는 Character이다!", "Wizard는 Character이다!"
반대로 "A가 B를 가진다! (has-a)" 관계라면,
상속보다 필드를 통한 포함(Composition)이 더 자연스럽습니다.
예: "캐릭터가 인벤토리를 가진다!" ⮕ "Character가 Inventory를 필드로 가진다!"