CodeGym /행동 /C# SELF /상속 소개

상속 소개

C# SELF
레벨 12 , 레슨 2
사용 가능

1. 소개

오늘 주제는 좀 추상적으로 느껴질 수도 있는데, 진짜 이거 없으면 앞으로 못 나가. 오늘은 상속에 대해 얘기할 거야. 오늘은 살짝 맛만 보고, 나중에 더 깊게 파헤칠 거야.

예를 들어, 너가 동물원 동물 관리 프로그램을 만든다고 해보자. 사자, 호랑이, 코끼리, 앵무새 등 여러 동물이 있어. 다 동물이긴 한데, 각자 특징도 있지:

  • 사자는 갈기가 있어.
  • 호랑이는 줄무늬가 있어.
  • 코끼리는 엄청 크고 코가 길어.
  • 앵무새는 말을 할 수 있어.

만약 각각의 동물을 따로따로 코드로 만든다면, 코드가 엄청 비슷해질 거야:


// 사자
public class Lion
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string Species { get; set; } = "사자"; // 항상 "사자"
    public void Eat() { Console.WriteLine("사자가 고기를 먹는다."); }
    public void Sleep() { Console.WriteLine("사자가 잔다."); }
    public void Roar() { Console.WriteLine("사자가 포효한다!"); }
    public string ManeColor { get; set; } // 사자만의 특징 - 갈기
}

// 호랑이
public class Tiger
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string Species { get; set; } = "호랑이"; // 항상 "호랑이"
    public void Eat() { Console.WriteLine("호랑이가 고기를 먹는다."); }
    public void Sleep() { Console.WriteLine("호랑이가 잔다."); }
    public void Stride() { Console.WriteLine("호랑이가 살금살금 걷는다."); }
    public string StripePattern { get; set; } // 호랑이만의 특징 - 줄무늬
}

코드 중복이 엄청 많지? Name, Age, Eat(), Sleep() — 이건 모든 동물에 공통이야! 만약 동물 종류가 100개라면? 끔찍하지!

여기서 상속이 빛을 발하는 거지.

2. C#에서 클래스 상속이란?

상속은 객체지향 프로그래밍(OOP)의 기본 원칙 중 하나야. 이미 존재하는 클래스를 기반으로 새로운 클래스를 만들 수 있게 해줘. 새로운 클래스(자식 클래스, 파생 클래스)는 부모 클래스(기본 클래스, 부모 클래스)의 모든 속성(필드)과 동작(메서드)을 물려받아.

현실에서도 비슷하지: 부모님한테 어떤 특징을 물려받지만, 너만의 고유한 특징도 있잖아.

C#에서 상속은 자식 클래스 이름 뒤에 콜론 :을 붙여서 표현해:


            // 부모(기본) 클래스 - Animal
public class Animal
{
    public string Name { get; set; } // 동물 이름
    public int Age { get; set; }     // 동물 나이

    public void Eat()
    {
        Console.WriteLine($"{Name}가 먹는다.");
    }

    public void Sleep()
    {
        Console.WriteLine($"{Name}가 잔다.");
    }
}

// 자식(파생) 클래스 - Lion은 Animal을 상속받음
public class Lion : Animal // <-- 이게 바로 상속!
{
    public string ManeColor { get; set; } // 사자만의 속성

    public void Roar() // 사자만의 동작
    {
        Console.WriteLine($"{Name}가 포효한다: 으르렁!");
    }
}

// 또 다른 자식 클래스 - Tiger는 Animal을 상속받음
public class Tiger : Animal
{
    public string StripePattern { get; set; } // 호랑이만의 속성

    public void Stride() // 호랑이만의 동작
    {
        Console.WriteLine($"{Name}가 조용히 살금살금 걷는다.");
    }
}

이제 Lion이나 Tiger 객체를 만들면, NameAge 속성, Eat()Sleep() 메서드를 자동으로 가지게 돼. 왜냐면 Animal에서 상속받았으니까!


class Program
{
    static void Main(string[] args)
    {
        Lion simba = new Lion();
        simba.Name = "심바";
        simba.Age = 5;
        simba.ManeColor = "골드";

        simba.Eat();   // Animal에서 상속받은 메서드
        simba.Sleep(); // Animal에서 상속받은 메서드
        simba.Roar();  // Lion만의 메서드

        Console.WriteLine($"{simba.Name} - 나이: {simba.Age}, 갈기 색: {simba.ManeColor}");

        Tiger shereKhan = new Tiger();
        shereKhan.Name = "셰르칸";
        shereKhan.Age = 7;
        shereKhan.StripePattern = "클래식";

        shereKhan.Eat();   // Animal에서 상속받은 메서드
        shereKhan.Sleep(); // Animal에서 상속받은 메서드
        shereKhan.Stride(); // Tiger만의 메서드

        Console.WriteLine($"{shereKhan.Name} - 나이: {shereKhan.Age}, 무늬: {shereKhan.StripePattern}");
    }
}

상속의 핵심 아이디어:

  1. 코드 재사용성(Code Reusability): 공통 로직을 기본 클래스에 넣어서 코드 중복을 피할 수 있어.
  2. 계층 구조(Hierarchy): "일반-특수" 구조가 만들어져 (AnimalLion/Tiger).
  3. 다형성(Polymorphism): (지금은 이름만 언급, 자세한 건 나중에!) 자식 클래스 객체를 부모 클래스 타입으로 다룰 수 있어. 예를 들어 Animal 리스트에 사자, 호랑이 다 넣고 Eat() 메서드를 호출할 수 있지.

3. 기본 클래스 생성자 호출 (base)

자식 클래스 객체(예: new Lion())를 만들면, C#은 자동으로 자식 클래스 생성자 전에 기본 클래스(Animal) 생성자를 호출해. 이 덕분에 부모 부분이 제대로 초기화되지!

가끔은 부모 생성자에 파라미터를 넘겨야 할 때가 있어. 이럴 때 자식 생성자 시그니처 뒤에 base 키워드를 써.


public class Animal
{
    public string Name { get; set; }
    public int Age { get; set; }

    // 기본 클래스 생성자
    public Animal(string name, int age)
    {
        Name = name;
        Age = age;
        Console.WriteLine($"Animal 생성자: 동물 {Name} 생성, 나이 {Age}.");
    }

    public void Eat()
    {
        Console.WriteLine($"{Name}가 먹는다.");
    }

    public void Sleep()
    {
        Console.WriteLine($"{Name}가 잔다.");
    }
}

public class Lion : Animal
{
    public string ManeColor { get; set; }

    // Lion 생성자에서 Animal 생성자 호출
    public Lion(string name, int age, string maneColor) : base(name, age) // <-- 이게 바로 'base'!
    {
        ManeColor = maneColor;
        Console.WriteLine($"Lion 생성자: 갈기 색 {ManeColor}.");
    }

    public void Roar()
    {
        Console.WriteLine($"{Name}가 포효한다: 으르렁!");
    }
}

            class Program
{
    static void Main(string[] args)
    {
        // Lion을 만들면 먼저 Animal 생성자, 그 다음 Lion 생성자 호출됨    
        Lion simba = new Lion("심바", 5, "골드");
        simba.Roar();
    }
}

new Lion("심바", 5, "골드") 실행 시 일어나는 일:

  1. Lion(string name, int age, string maneColor) 생성자가 호출됨.
  2. : base(name, age) 덕분에 Animal(string name, int age) 생성자로 제어가 넘어감.
  3. Animal 생성자 코드가 실행되고, Animal 생성자: 동물 심바 생성, 나이 5.가 출력됨.
  4. 그 다음 제어가 Lion 생성자로 돌아옴.
  5. Lion 생성자 코드가 실행되고, Lion 생성자: 갈기 색 골드.가 출력됨.

이게 진짜 중요해: 부모 클래스가 항상 먼저 초기화된다!

4. isas 연산자

C# 프로그래밍 하다 보면, 객체의 타입을 검사하거나 다른 타입으로 변환해야 할 때가 많아. 이럴 때 isas 연산자가 등장하지! 상속, 다형성 쓸 때 특히 많이 쓰는데, 지금은 기본만 알아두자.

is 연산자 (타입 검사)

is 연산자는 객체가 특정 타입과 호환되는지 검사해. 변환 가능하면 true, 아니면 false를 반환하지.

문법: 표현식 is 타입


static void AnalyzeObject(object obj)
{
    if (obj is string)
    {
        Console.WriteLine("이건 문자열이야!");
    }
    else if (obj is int)
    {
        Console.WriteLine("이건 정수야!");
    }
    else
    {
        Console.WriteLine("이건 다른 거야.");
    }
}

static void Main()
{
    AnalyzeObject("안녕, 세상!");      // 이건 문자열이야!
    AnalyzeObject(123);                 // 이건 정수야!
    AnalyzeObject(3.14);                // 이건 다른 거야.
    AnalyzeObject(new int[] { 1, 2 });  // 이건 다른 거야.
}

is와 패턴 매칭:

최신 C#에서는 is로 타입 검사하면서 동시에 변수 선언도 할 수 있어. 이걸 "패턴 매칭"이라고 하고, 코드가 훨씬 간결해져.


static void AnalyzeObjectWithPatternMatching(object obj)
{
    if (obj is string message) // obj가 문자열이면 message에 할당
    {
        Console.WriteLine($"이건 문자열, 길이: {message.Length}");
    }
    else if (obj is int number) // obj가 int면 number에 할당
    {
        Console.WriteLine($"이건 숫자, 2배: {number * 2}");
    }
    else
    {
        Console.WriteLine("타입을 알 수 없음.");
    }
}

static void Main()
{
    AnalyzeObjectWithPatternMatching("예시"); // 이건 문자열, 길이: 2
    AnalyzeObjectWithPatternMatching(50);       // 이건 숫자, 2배: 100
    AnalyzeObjectWithPatternMatching(new object()); // 타입을 알 수 없음.
}

is와 패턴 매칭 방식이, 옛날 방식(타입 변환 후 null 체크)보다 훨씬 안전하고 읽기 좋아.

as 연산자 (안전한 타입 변환)

as 연산자는 객체를 특정 타입으로 변환하려고 시도해. 변환 가능하면 변환된 객체를 반환하고, 불가능하면 null을 반환해(예외 안 던짐). 그래서 as는 "안전한" 타입 변환 연산자야.

문법: 표현식 as 타입


static void ProcessData(object data)
{
    string text = data as string; // data를 string으로 변환 시도
    
    if (text != null) // 변환 성공 여부 확인
    {
        Console.WriteLine($"문자열 처리: {text.ToUpper()}");
    }
    else
    {
        Console.WriteLine("데이터가 문자열이 아님.");
    }
}

static void Main()
{
    ProcessData("hello");      // 문자열 처리: HELLO
    ProcessData(123);          // 데이터가 문자열이 아님.
    ProcessData(null);         // 데이터가 문자열이 아님.
}

as를 직접 변환 (타입)표현식 대신 쓸 때:

  • 안전성: 객체가 진짜 원하는 타입인지 확신이 없을 때, as를 쓰면 InvalidCastException 없이 null을 받아서 처리할 수 있어.
  • 참조 타입만: as는 참조 타입(클래스, 인터페이스, 델리게이트, 배열)에만 쓸 수 있어. 값 타입(예: int에서 double로)은 안 돼.

중요: 객체가 항상 원하는 타입임을 확신하거나, 값 타입 변환이 필요하면 직접 변환 (타입)표현식을 써. 변환 불가 시 직접 변환은 에러(InvalidCastException)를 던지니까, 타입이 잘못된 게 진짜 버그일 때는 이게 더 좋아.

코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION