[20251223] 정적/상속/추상

SmartBear·2025년 12월 23일

정적(Static) 클래스

한정자 정적 - MSDN

Python 에서도 static을 많이 썼는데 C# 과는 조금 다른 것 같다.

특징Python (@staticmethod)C# (static)
암묵적 인자받지 않음 (self/cls 없음)받지 않음 (self/cls 없음)
클래스/인스턴스 접근직접 접근 불가 (상태 변경 불가)static 멤버만 접근 가능 (인스턴스 멤버 접근 불가)
주요 목적논리적 그룹화, 코드 구성클래스 자체에 속한 기능 제공, 상태 공유
언어 특징동적 타입정적 타입
  • static 은 메모리에 정적으로 메모리를 할당.
    • 잘쓰면 강력하지만 잘못쓰면 불필요한 메모리 사용하게됨
    • 다만, 그렇게 많은 메모리를 먹지는 않기 때문에 무서워할 필요는 없음.
  • static 은 관련 클래스/함수/변수등이 사용되는 시점에 메모리에 할당되며 그 이후 프로그램 종료시까지 유지됨.
  • 클래스내 static으로 선언된 것들은 instanciateinstance들 간 공유된다. (전역)

코드 예시

// StaticStudy.cs

namespace OOP;

public class StaticStudy
{
    public static string IAM = "정적 클래스";
    public static int StaticValue = 0;
    public int A;
    public int B;
    public static void IntroduceMyself()
    {
        Console.WriteLine("나는 정적 클래스내 정적 함수 입니다.");
    }

    static StaticStudy()
    {
        // 인스턴스 생성시
        // 정적 값/함수 접근
        // 접근 안하면 호출 X
        // 여러 인스턴스 호출 => 한번만!
        // static 으로 지정된 데이터만 제어 가능
        Console.WriteLine("Static 생성자!");
    }
    public StaticStudy(int a, int b)
    {
        A = a;
        B = b;
        Console.WriteLine("퍼블릭 생성자!");
    }
    
}

// Main.cs
using System;

namespace OOP
{
    public class Program
    {
         public static void Main(string[] args)
        {
            // StaticStudy study = new(); instantiate 는 되지 않는다.
            Console.WriteLine(StaticStudy.IAM);
            // 출력: Static 생성자!
            //      정적 클래스
            StaticStudy.IntroduceMyself();
            // 출력: 나는 정적 클래스내 정적 함수 입니다.
            StaticStudy staticStudy = new(1, 2);
            // 출력: 퍼블릭 생성자!
            Console.WriteLine($"A:{staticStudy.A} | B:{staticStudy.B}");
            // 출력: A:1 | B:2
            
            // static 은 메모리를 정적으로 할당하는 것이기 때문에 값타입의 경우 값을 변경할 수 있다.
            // (const개념이 아님!)
            Console.WriteLine(++StaticStudy.StaticValue);
            Console.WriteLine(StaticStudy.StaticValue++);
            Console.WriteLine(StaticStudy.StaticValue);
            
            // Class 내 Static 값은 전역이기 때문에 모든 Instance 가 동일한 값을 갖게 된다.
            StaticStudy staticStudy001 = new(0, 0);
            // 출력: 퍼블릭 생성자!
            StaticStudy staticStudy002 = new(0, 0);
            // 출력: 퍼블릭 생성자!
            
            StaticStudy.StaticValue = 100;
            
            staticStudy001.A = StaticStudy.StaticValue;
            staticStudy002.A = StaticStudy.StaticValue;
            
            Console.WriteLine($"staticStudy001.A:{staticStudy001.A}"); 
            // 출력: 100
            Console.WriteLine($"staticStudy002.A:{staticStudy002.A}"); 
            // 출력: 100
            
        }
    }
}

상속(Inherit) 클래스

Python 클래스에서도 자주 사용했던 개념. 부모/자식 관계에 대한 디자인이 필요한 부분이다.

부모 클래스에서는 주로 공통적인 코드를 넣고, 자식 클래스에서는 상속을 받아 해당 클래스에서만 사용해야하는 기능을 추가 하는 방식이 보통이다.

  • 보통 Parent Class 에 있는 필드나 함수는 Child Class에서 접근이 가능하다.
  • 단, Child Class에 선언된 필드나 함수는 Parent Class에서는 접근이 불가능 하다.

코드 예시 1

// InheritStudy01.cs
namespace OOP;

public class InheritStudy01
{
    public int a = 10;
    protected int protectedValue = 1_000;
    private int privateValue = 10_000;
    public InheritStudy01()
    {
        Console.WriteLine("부모 생성자");
    }

    public void PrintParentField()
    {
        Console.WriteLine($"a: {a}");
    }
}

public class Child01 : InheritStudy01
{
    public int b = 1;
    public Child01()
    {
        Console.WriteLine("Child01 생성자");
        Console.WriteLine($"protectedValue: {protectedValue}");
        // Console.WriteLine($"privateValue: {privateValue}");  접근 할 수 없다!
    }

    public void PrintSum()
    {
        Console.WriteLine($"a + b = {a + b}");
    }
}

public class Child02 : InheritStudy01
{
    public int b = 2;
    public Child02() : base()  // 부모 클래스내 생성자가 protected 일 경우에 base 를 사용.
    {
        Console.WriteLine("Child02 생성자");
    }
    
    public void PrintMultiplier()
    {
        Console.WriteLine($"a x b = {a * b}");
    }
}


// InheritStudy02.cs
namespace OOP;

public class InheritStudy02
{
    public int A;
    protected InheritStudy02(int a)
    {
        A = a;
    }
}

public class Child0202 : InheritStudy02
{
    public int B;
    public Child0202(int a, int b) : base(a)
    {
        A = a;
        B = b;
    }
}


// Main.cs
using System;

namespace OOP
{
    public class Program
    {
         public static void Main(string[] args)
        {
            InheritStudy01 parent = new InheritStudy01();
            // 출력: 부모 생성자
            Child01 child0101 = new Child01();
            // 출력: 부모 생성자
            //      Child01 생성자
            //      protectedValue: 1000
            Child01 child0102 = new Child01();
            // 출력: 부모 생성자
            //      Child01 생성자
            //      protectedValue: 1000
            Child02 child0201 = new Child02();
            // 출력: 부모 생성자
            //      Child02 생성자

            // parent.PrintSum(); 부모는 자식의 함수를 접근할 수 없다.
            // parent.protectedValue; Protected 는 외부에서 사용할 수 없다.
            child0101.PrintParentField();
            // 출력: a: 10
            // child0101.protectedValue; 자식 또한 protectedValue 를 외부에서 접근할 수 없다.
            child0101.PrintSum();
            // 출력: a + b = 11
            child0201.PrintMultiplier();
            // 출력: a x b = 20
            // child0201.PrintSum(); 당연하지만 접근 안된다.

            Child0202 child0202 = new(10, 20);
            Console.WriteLine($"child0202.A: {child0202.A}");
            // child0202.A: 10
            Console.WriteLine($"child0202.B: {child0202.B}");
            // child0202.B: 20
        }
    }
}

접근 한정자

부모 클래스 Protected 사용시 생성자 관련

부모 클래스에서 생성자에 protected 를 사용할 경우 base 함수를 사용하여 부모 생성자부터 실행할 수 있도록 할 수 있다.

Overload 과 Override

Python 에서는 재정의의 개념이 있었지만, 명시적인 키워드를 이용하지는 않았다. 때문에 이 부분은 생소하다.
(오히려 JAVA 에서 슥 지나가며 알게된 느낌이..)

  • Overload: Overload 는 같은 Class 내 (상속 포함) 같은 이름의 Method 를 사용하는 방법이다. 매게변수를 받는 방법에 따라 같은 이름으로 Method 를 다르게 실행할 수 있다.
  • Override: 부모 클래스에서 명시적으로 virtual 이나 abstract 타입을 선언한 Method에 대한 재정의를 한다.

Type Casting

  • Up Casting 은 자식 Class 를 이용하여 부모 Class 의 Type 으로 인스턴스화. 이때는 암묵적 형변환 진행.
  • Down Casting 은 부모 Class 를 이용하여 자식 Class 의 Type 으로 인스턴스화. 이때는 명시적 형변환 필요.
    • is ; Down casting 관련이며, bool 타입을 리턴한다.
    • as ; Down casting 관련이며 올바른 경우 해당 객체를 리턴한다. 그렇지 않으면 null을 리턴한다.

코드 예시 2

// Monster.cs
using System;

public class Monster
{
    protected string Name = "SYSTEM";
    protected string Voice;

    protected Monster(string name)
    {
        Log($"{name}(이)가 나타났다!");
    }
    
    public void Log(string message)
    {
        Console.WriteLine($"[{Name}]: {message}");
    }
    
    public virtual void Attack(int damage)
    {
        Log($"{damage} 의 피해를 입혔다.");
    } 
    
    public void RunVoice()
    {
        Log(Voice);
    }
}

public sealed class Wolf : Monster
{
    public Wolf() : base("늑대")
    {
        Name = "늑대";
        Voice = "아우우우우우";
    }

    public override void Attack(int damage)
    {
        base.Attack(damage);
        Log("깨게겡");
    }

    public void Run()
    {
        Log("도망간다");
    }

    public void Run(string direction)
    {
        Log($"{direction}으로 도망간다");
    }
}

public sealed class Goblin : Monster
{
    public Goblin() : base("고블린")
    {
        Name = "고블린";
        Voice = "고브고브!";
    }
    
    public override void Attack(int damage)
    {
        base.Attack(damage);
        Log("꾸에엑");
    }

    public void Dance()
    {
        Log("덩실덩실~ 즐겁구나~~");
    }
}

// Main.cs
using System;

namespace OOP
{
    public class Program
    {
         public static void Main(string[] args)
        {
            Monster goblin = new Goblin();  // 암시적 형변환
            // 출력: [SYSTEM]: 고블린(이)가 나타났다!
            goblin.RunVoice();
            // 출력: [고블린]: 고브고브!
            goblin.Attack(100);
            // 출력: [고블린]: 100 의 피해를 입혔다.
            //      [고블린]: 꾸에엑
            
            Wolf wolf = new Wolf();
            // 출력: [SYSTEM]: 늑대(이)가 나타났다!
            wolf.RunVoice();
            // 출력: [늑대]: 아우우우우우
            wolf.Attack(200);
            // 출력: [늑대]: 200 의 피해를 입혔다.
            //      [늑대]: 깨게겡
            wolf.Run("동쪽");
            // 출력: [늑대]: 동쪽으로 도망간다
            wolf.Run();
            // 출력: [늑대]: 도망간다
            
            Console.WriteLine($"goblin is Goblin => {goblin is Goblin}");
            // 출력: goblin is Goblin => True
            Console.WriteLine($"goblin as Goblin => {goblin as Goblin}");
            // 출력: goblin as Goblin => Goblin
            (goblin as Goblin)?.Dance();
            // 출력: [고블린]: 덩실덩실~ 즐겁구나~~
        }
    }
}

추상(Abstract) 클래스

Python 에서 abc.abstract 데코레이터를 사용했던 경험과 같은 개념이다.

상속이라는 개념을 이용하여 부모 클래스에서 자식 클래스에게 구현해야 하는 내용에 대해 강제성을 제공하는 기능이다. 이것은 협업이 많은 개발 업무에서 휴먼에러를 줄일수 있는, 언어에서 제공하는 일종의 안전장치라고 볼 수 있다. 또한 디자인적으로도 굉장히 좋은 기능이다. 일반적으로는 Top -> Down 방식으로 디자인 하는 경우가 많기 때문에 추상화된 개념을 먼저 구현하고, 상세내용을 구현하는 경우가 많다.

코드 예시

// AbstractExample.cs
using System;

public abstract class ParentCls
{
    public abstract void Enter();
    public abstract void ShowName();
    public abstract void Exit();
}

public class ChildCls : ParentCls
{
    public override void Enter()
    {
        Console.WriteLine("Child Enter");   
    }

    public override void ShowName()
    {
        Console.WriteLine("Child ShowName");
    }

    public override void Exit()
    {
        Console.WriteLine("Child Exit");
    }
}

// Main.cs

namespace Examples
{
    class Program
    {
        static void Main(string[] args)
        {
            // ParentCls p = new(); 당연하지만 안된다.
            ChildCls childCls = new ChildCls();
            childCls.Enter();
            // 출력: Child Enter
            childCls.ShowName();
            // 출력: Child ShowName
            childCls.Exit();
            // 출력: Child Exit
        }
    }
}
profile
Python Dev with Infra -> Game Programmer

0개의 댓글