no image
C# - base 키워드
✅ base 키워드란?자식 클래스에서 부모 클래스의 생성자나 메서드, 필드에 접근할 때 사용하는 키워드📌 1. 부모 클래스의 메서드 호출할 때class Parent{ public void Speak() { Console.WriteLine("부모가 말한다"); }}class Child : Parent{ public void Say() { base.Speak(); // 부모의 Speak() 호출 }}📌 2. 부모 생성자 호출class Parent{ public Parent(string name) { Console.WriteLine($"{name} 생성됨"); }}class Child : Parent{ public ..
2025.07.03
C#
no image
Unity - (Template vs Template Method Pattern) vs (Class vs Instance)
❓템플렛 메서드 패턴에 대해서 공부하다가 너무 궁금한게 생겼다 그럼 템플릿이랑 템플릿 메서드 패턴의 차이점이 뭐지 싶었다. "템플릿"은 그냥 일반적인 용어이고,"템플릿 메서드 패턴"은 디자인 패턴이다.즉, 템플릿은 개념,템플릿 메서드 패턴은 그 개념을 객체지향 코드에 적용한 구체적인 방식이에요.📌 템플릿(Template) = “틀”, “형식”그냥 일반적인 단어로, 어떤 공통된 구조, 순서, 뼈대를 말함예:웹사이트에서 “이메일 템플릿”게임에서 “스킬 이펙트 템플릿”Unity에서 ScriptableObject 템플릿“이걸 따라 만들어” 라는 공통 기준/포맷🧱 템플릿 메서드 패턴 (Template Method Pattern)이 "틀"이라는 개념을 OOP에서 상속 + 다형성 + 추상화를 이용해 구현한 디자인..
2025.07.03
no image
DesignPattern - Template Method Pattern(템플릿 메서드 패턴) vs virtual
✅ 템플릿 메서드 패턴이란?**템플릿 메서드 패턴(Template Method Pattern)**은상속을 기반으로 부모 클래스에서 **알고리즘의 전체 흐름(틀)**을 정의하고,일부 단계는 abstract 또는 virtual로 정의하여 자식 클래스가 자유롭게 재정의할 수 있도록 하는 패턴입니다. 참고 영상https://www.youtube.com/watch?v=TfwHAT1H04I 🧱 핵심 구성요소템플릿 클래스PlayerBase공통 기능 (Idle(), Move())은 virtual, 꼭 구현해야 하는 핵심 기능은 abstract로 정의구체 클래스Warrior, MagicianPlayerBase를 상속받아 필요한 메서드들을 override클라이언트TestPlayerBase 타입으로 실제 자식 인스턴스를 ..
2025.07.03
no image
C# - Class Inheritance(클래스 상속)& Interface(인터페이스) vs abstract Class
✅ 클래스 상속 (Inheritance)✔️ 정의class A : B처럼, 한 클래스가 다른 클래스의 기능을 물려받는 것✔️ 목적코드 재사용: 부모 클래스의 멤버(필드, 메서드 등)를 자식이 그대로 사용 가능기능 확장: 자식 클래스는 새로운 기능을 추가하거나, 부모 기능을 재정의 가능다형성(polymorphism): 부모 타입으로 자식 객체를 다룰 수 있음✔️ 예제class Character{ public virtual void Attack() { Console.WriteLine("기본 공격!"); }}class Warrior : Character{ public override void Attack() { Console.WriteLine("칼 휘두르기!..
2025.07.03
C#
no image
C# - Virtual & abstract
✅ virtual 키워드 정리📌 정의virtual은 부모 클래스에서 메서드나 속성을 "재정의 가능"하게 만드는 키워드입니다.📋 기본 문법class Parent{ public virtual void SayHello() { Console.WriteLine("안녕하세요. 부모입니다."); }}class Child : Parent{ public override void SayHello() { Console.WriteLine("안녕하세요. 자식입니다."); }}✅ 주요 특징기본 구현 제공부모 클래스가 기본 로직을 정의자식 클래스에서 변경 가능override 키워드로 오버라이딩 가능선택적 재정의자식 클래스에서 반드시 재정의하지 않아도 됨다형성 지원부모 타..
2025.07.03
C#
no image
TIL - 내일배움캠프 3일차 TIL [CS공부 + 카드게임 팀 프로젝트](25.07.02)
오늘 하루 계획오전09:00 ~ 13:00 : CS 공부어제 내용 복습 - GC, 해시테이블, 딕셔너리, .NET 런타임, 캡슐화, 박싱/언박싱 & 형변환HashTableDictionaryHashSet제네릭 (Generics)ScriptableObject오후13:00 ~ 14:00 : 점심시간14:00 ~ 18:00 : 팀 프로젝트 진행 (12조)폭탄 스킬 기능 구현 및 사운드 연결카드 수량에 따라 3x3 / 4x4 자동 배치 처리게임오버 사운드 추가GameManager, Card, Board 등 스크립트 리팩토링ScriptableObject Stage 데이터 기반 동작 적용 완료18:00 ~ 19:00 : 저녁시간저녁19:00 ~ 20:00 : 팀 회의20:00 ~ 20:30 : 개인 TIL 작성20:..
2025.07.02
no image
C# - 자료구조
✅ 자료구조란?자료(데이터)를 효율적으로 저장하고, 관리하고, 사용할 수 있도록 구성한 방식쉽게 말하면,"데이터를 어떻게 정리해서 넣고, 꺼내고, 수정할지에 대한 규칙과 구조"✅ 왜 중요한가?빠르게 찾기 위해효율적으로 저장하기 위해복잡한 데이터를 쉽게 다루기 위해성능 좋은 알고리즘을 만들기 위해✅ 비유로 설명하면자료구조 = 서랍장예를 들어, 옷을 아무데나 넣으면 나중에 찾기 힘들지?자료구조는 "양말은 맨 위 칸", "바지는 아래 칸"처럼 정리 기준을 정해주는 거야.✅ 주요 자료구조 종류 (기초)자료구조설명예시배열 (Array)순서대로 데이터를 저장리스트, 숫자 모음리스트 (List)동적으로 크기가 변하는 배열C#의 List스택 (Stack)나중에 넣은 게 먼저 나옴 (LIFO)웹 브라우저 뒤로가기큐 (Q..
2025.07.02
C#
no image
C# - 해쉬셋(HashSet) vs Dictionary(HashTable)
✅ HashSet이란?중복을 허용하지 않는 데이터 집합 (Set)을 구현한 자료구조내부적으로는 **해시 테이블(Hash Table)**을 사용하지만,핵심 목적은 **"중복 없는 값의 모음"**을 만드는 거야.🔍 HashSet의 특징❌ 중복 허용 안 함같은 값을 두 번 넣어도 한 번만 저장됨🔍 순서 없음넣은 순서 보장 안 됨⚡ 매우 빠름추가, 삭제, 탐색 전부 평균 O(1)📦 내부 구조키만 있고, 값은 없음 (딕셔너리는 key-value, 얘는 key만 존재)✅ HashSet 예제 (C#)using System.Collections.Generic;HashSet names = new HashSet();names.Add("Alice");names.Add("Bob");names.Add("Alice"); ..
2025.07.02
C#

C# - base 키워드

Dev_Jen
|2025. 7. 3. 12:26
반응형

✅ base 키워드란?

자식 클래스에서 부모 클래스의 생성자나 메서드, 필드에 접근할 때 사용하는 키워드


📌 1. 부모 클래스의 메서드 호출할 때

class Parent
{
    public void Speak()
    {
        Console.WriteLine("부모가 말한다");
    }
}

class Child : Parent
{
    public void Say()
    {
        base.Speak(); // 부모의 Speak() 호출
    }
}

📌 2. 부모 생성자 호출

class Parent
{
    public Parent(string name)
    {
        Console.WriteLine($"{name} 생성됨");
    }
}

class Child : Parent
{
    public Child() : base("자식이지만 부모도 초기화") 
    {
        Console.WriteLine("자식 생성자 실행");
    }
}

📌 3. 오버라이드한 메서드 안에서 부모의 원본 호출

class Character
{
    public virtual void Attack()
    {
        Console.WriteLine("기본 공격!");
    }
}

class Warrior : Character
{
    public override void Attack()
    {
        base.Attack(); // 부모의 기본 공격도 같이 실행됨
        Console.WriteLine("검 휘두르기!");
    }
}

💡 언제 쓰는가?

부모의 로직을 확장하고 싶을 때 base.메서드() 호출
부모의 생성자 초기화가 필요한 경우 : base(...) 사용
부모의 기본 동작도 유지하고 싶을 때 오버라이드 안에서 base 사용
 

⚠️ 주의

  • base는 상속 관계에서만 사용 가능
  • 자식이 부모를 가리킬 때만 쓸 수 있음
  • base는 부모 클래스의 "구현된 기능"에 접근할 때 쓰는 거지, 추상 인터페이스에는 직접 쓰지 않음

✅ 한 줄 정리

base는 자식 클래스에서 "부모 클래스의 기능을 호출"할 수 있도록 해주는 키워드야.

 

흠..잘 알겠지만 이건 잘 사용하지 않을듯한 느낌이 든다.. 아직 내가 유니티에서 사용하지 않아봐서 그럴지도 모른다.

그래서 GPT한테 base 키워드에한 중요성을 물어봤다!

 

진짜 좋은 질문이야.
많은 사람이 처음에 base를 보면 “어? 굳이 이걸 꼭 써야 하나?”,
“내가 override 해서 다 재정의하면 되지 않나?” 이런 생각을 해.
근데 실제로는 잘 쓰면 엄청 유용하고, 안 쓰면 부모 클래스 기능을 날려먹을 수 있어.


✅ base의 중요성은 “부모 기능을 이어받되, 추가하거나 확장할 수 있게 해주는 것”이야.


🎯 base를 안 쓰면 생기는 문제

예시: 캐릭터 기본 공격을 무시해버림

class Character
{
    public virtual void Attack()
    {
        Console.WriteLine("기본 공격!");
    }
}

class Warrior : Character
{
    public override void Attack()
    {
        Console.WriteLine("워리어 스킬 발동!");
    }
}
  • 👉 이 경우 기본 공격 출력 안 됨
  • Character의 공격 로직이 완전히 무시됨 → 부모 기능 상실

✅ base를 써서 부모 기능도 유지

class Warrior : Character
{
    public override void Attack()
    {
        base.Attack();  // 부모의 기본 공격 먼저 실행
        Console.WriteLine("워리어 스킬 발동!");
    }
}

➡️ 결과:

 
기본 공격!
워리어 스킬 발동!

✅ → 부모 로직 + 자식 로직이 순차적으로 실행됨 = 기능 확장


🔥 실전에서 base를 잘 쓰는 대표적인 상황

UI 초기화, 애니메이션 제어 부모가 기본 처리 다 해놓고, 자식은 추가 효과만 넣고 싶을 때
MonoBehaviour의 Start(), Update() 재정의 자식에서 base.Start() 호출 안 하면 부모의 Start 로직이 사라짐
디버깅, 추적, 로깅 부모에서 로깅 다 해놓고, 자식이 그 위에 로직 덧붙일 수 있음
템플릿 메서드 패턴 부모의 흐름 유지하면서 일부만 바꿔야 할 때 base로 확장 가능

❗ 안 쓰면 벌어지는 일

  • 부모 클래스의 중요한 로직이 사라짐
  • 자식 클래스가 전체 흐름을 깨뜨려버림
  • 유지보수할 때 “왜 안 되지?”하고 원인을 찾기 어려워짐

✅ 한 줄 정리

base는 **"부모의 로직을 살리면서 자식에서 기능을 확장"**하고 싶을 때 정말 중요한 도구야.

 

오 그래도 이렇게 보니까 base의 기능이나 필요성을 잘 알거같다 나중에 유니티 개발할때 꼭 사용해봐야겠다!

반응형
반응형

템플렛 메서드 패턴에 대해서 공부하다가 너무 궁금한게 생겼다 그럼 템플릿이랑 템플릿 메서드 패턴의 차이점이 뭐지 싶었다.

 

"템플릿"은 그냥 일반적인 용어이고,
"템플릿 메서드 패턴"은 디자인 패턴이다.

즉, 템플릿은 개념,
템플릿 메서드 패턴은 그 개념을 객체지향 코드에 적용한 구체적인 방식이에요.


📌 템플릿(Template) = “틀”, “형식”

  • 그냥 일반적인 단어로, 어떤 공통된 구조, 순서, 뼈대를 말함
  • 예:
    • 웹사이트에서 “이메일 템플릿”
    • 게임에서 “스킬 이펙트 템플릿”
    • Unity에서 ScriptableObject 템플릿

“이걸 따라 만들어” 라는 공통 기준/포맷


🧱 템플릿 메서드 패턴 (Template Method Pattern)

  • 이 "틀"이라는 개념을 OOP에서 상속 + 다형성 + 추상화를 이용해 구현한 디자인 패턴
  • 부모 클래스에서 전체 **알고리즘의 흐름(템플릿 메서드)**을 만들고
    자식 클래스가 일부 단계를 구현하거나 수정함
public void StartGame() {
    LoadAssets(); // virtual
    Init();       // abstract
    Play();       // virtual
    End();        // virtual
}

➡️ 이게 바로 템플릿 메서드 (즉, 틀!)
➡️ 그래서 “메서드 수준의 템플릿 구조” = 템플릿 메서드 패턴


🔍 예시 비교

템플릿 구조, 형식, 기본 틀 PPT 템플릿, UI 템플릿, HTML 템플릿
템플릿 메서드 패턴 상속 + 오버라이딩으로 흐름 제어 StartGame() 내에서 흐름 고정, 단계마다 override

 


✅ 쉽게 한 줄로 요약

“템플릿은 개념이고,
템플릿 메서드 패턴은 그 개념을 코드 구조에 적용한 디자인 패턴이다.”

 

흠..그러면 템플릿은 기본 형식 틀이고 템플릿 메서드 패턴은 템플릿을 사용해서 만든 기능등등을 얘기하는 듯하는데

그러면 클래스와 인스턴스와 비슷한 느낌이지 않나 싶다. 그래서 다시 물어봤다!

 

와… 네 감각 정말 좋아요.
지금 말한 “클래스의 틀 vs 인스턴스” 개념을
**“템플릿 vs 템플릿 메서드 패턴”**에 비유하는 건 굉장히 날카로운 통찰이에요.
결론부터 말하자면 **“비슷한 구조의 관계다”**라고 볼 수 있어요.
하지만 역할과 범위는 조금 다릅니다. 자세히 비교해볼게요 👇


✅ 클래스 vs 인스턴스

구분 클래스 인스턴스
정의 객체를 만들기 위한 설계도, 틀 그 설계도로 만들어진 실제 객체
역할 “어떻게 만들 건지”를 정의 “실제로 사용되는 대상”
비유 붕어빵 틀 붕어빵 한 개

✅ 템플릿 vs 템플릿 메서드 패턴

구분 템플릿(개념) 템플릿 메서드 패턴
정의 반복되는 구조, 고정된 형식, 패턴 그 자체 OOP에서 상속과 오버라이드로 흐름을 강제하고 확장하는 디자인 패턴
역할 어떤 “형태”를 따라야 한다는 구조적인 아이디어 그걸 실제 코드로 구현하는 방식
비유 알고리즘의 뼈대 그 뼈대를 부모 클래스에 메서드로 정의한 구조

🎯 그럼 네가 말한 비유는?

템플릿 : 템플릿 메서드 패턴
= 클래스 : 인스턴스

이 비유는 완전히 1:1은 아니지만, 매우 직관적인 이해 방식이야.

하나는 개념적 틀이고, 하나는 그걸 실제로 구현해서 쓰는 구조
템플릿이 있기에 템플릿 메서드 패턴이 가능하고,
클래스가 있기에 인스턴스를 만들 수 있음
템플릿은 “이런 흐름을 따라야 해”,
템플릿 메서드 패턴은 “그 흐름을 따르는 방식의 코드 구조”

✅ 한 줄 정리

네가 말한 건 비유적으로 정확해요.
템플릿은 개념적인 형식, 템플릿 메서드 패턴은 그걸 구현한 실체 구조
마치 클래스(설계도)와 인스턴스(실행 객체)처럼요.

 

GPT한테 칭찬도 받네여..ㅋㅋㅋ 그래도 비슷하긴하지만 확실히 다르죠 그냥 느낌이 비슷해보여서 생각한건데 말이죠..

하여간 그렇다고 합니다! 덕분에 잘 이해했네여!

반응형
반응형

✅ 템플릿 메서드 패턴이란?

**템플릿 메서드 패턴(Template Method Pattern)**은
상속을 기반으로 부모 클래스에서 **알고리즘의 전체 흐름(틀)**을 정의하고,
일부 단계는 abstract 또는 virtual로 정의하여 자식 클래스가 자유롭게 재정의할 수 있도록 하는 패턴입니다.

 

참고 영상

https://www.youtube.com/watch?v=TfwHAT1H04I

 


🧱 핵심 구성요소

템플릿 클래스 PlayerBase 공통 기능 (Idle(), Move())은 virtual, 꼭 구현해야 하는 핵심 기능은 abstract로 정의
구체 클래스 Warrior, Magician PlayerBase를 상속받아 필요한 메서드들을 override
클라이언트 Test PlayerBase 타입으로 실제 자식 인스턴스를 받아 실행 (다형성 활용)

🔍 코드 구조 분석

1. 템플릿 클래스: PlayerBase

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Template
{
    // 템플릿 클래스
    // 공통 기능(Idle, Move)은 virtual로 제공하고
    // 핵심 기능(MoveTarget, Attack)은 abstract로 정의해 자식이 반드시 구현하도록 강제함
    public abstract class PlayerBase : MonoBehaviour
    {
        public virtual void Idle()
        {
            // 공용 Idle 구현
        }

        public virtual void Move()
        {
            // 공용 Move 구현
        }

        public abstract void MoveTarget(); // 반드시 자식이 구현해야 할 핵심 로직
        public abstract void Attack();     // 반드시 자식이 구현해야 할 핵심 로직
    }
}
  • 공통 동작은 virtual로 정의하여 자식이 선택적으로 재정의
  • 핵심 필수 동작은 abstract로 정의하여 자식이 반드시 구현하게 강제

2. 구체 클래스: Warrior, Magician

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Template
{
    // Warrior 클래스는 PlayerBase를 상속받아
    // 추상 메서드를 구체적으로 구현한다
    public class Warrior : PlayerBase
    {
        public override void MoveTarget()
        {
            // 워리어 MoveTarget 구현
            // abstract로 받아서 직접 구현해줘야함
        }

        public override void Attack()
        {
            // 워리어 Attack 구현
            // abstract로 받아서 직접 구현해줘야함
        }
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Template
{
    // Magician 클래스는 PlayerBase를 상속받고
    // Move()까지 override해서 고유 이동 방식 정의
    public class Magician : PlayerBase
    {
        public override void Move()
        {
            // 메지션 Move 구현
            // 메지션은 따로 움직임이 필요해서 만든 것
        }

        public override void MoveTarget()
        {
            // 메지션 MoveTarget 구현
            // abstract로 받아서 직접 구현해줘야함
        }

        public override void Attack()
        {
            // 메지션 Attack 구현
            // abstract로 받아서 직접 구현해줘야함
        }
    }
}
  • Warrior는 기본 Move() 로직 사용 → override 안함
  • Magician은 이동이 특이하므로 Move()도 override

3. 실행 코드 (클라이언트): Test

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Template
{
    public class Test : MonoBehaviour
    {
        [SerializeField] PlayerBase _playerBase;

        private void Start()
        {
            // Idle
            _playerBase.Idle();

            // Move
            _playerBase.Move();

            // MoveTarget
            _playerBase.MoveTarget();

            // Attack
            _playerBase.Attack();

            // 사용한다면 이런식으로 사용한다.
            // 다양성을 이용해서 만들어놓은 워리어, 메지션 등 사용할 수 있다.
            // => 이게 바로 템플릿 메서드 패턴의 핵심! 알고리즘 틀은 고정, 세부는 다양
        }
    }
}
  • PlayerBase 타입 하나로 Warrior, Magician 등 다양한 자식 타입을 다룰 수 있음
  • 다형성(Polymorphism) 활용 → 코드 변경 없이 캐릭터 전환 가능

🎮 정리: 왜 템플릿 메서드 패턴이 좋은가?

장점설명
알고리즘의 흐름 고정 공통 로직은 부모가 제공하여 일관성 유지
세부 단계 유연화 자식 클래스가 필요한 부분만 재정의 가능
중복 제거 공통 코드 반복을 줄이고 관리 포인트를 줄임
다형성 활용 PlayerBase 하나로 모든 캐릭터를 통합해서 제어 가능
 

✅ 최종 요약

  • PlayerBase는 게임 캐릭터 로직의 골격을 정의한 템플릿 클래스
  • Warrior, Magician은 세부 구현을 담당하는 서브 클래스
  • Test는 추상 타입을 통해 다양한 캐릭터를 처리하는 클라이언트
  • abstract, virtual을 적절히 활용하여 강제성과 유연성의 균형을 유지

음..사실 템플릿 메서드가 영상을 보고도 왔지만 정확히 이해가 잘 되지 않았다 템플릿을 왜 쓰는걸까? 그냥 virtual이랑 abstract를 사용해서 상속받고 정의하고 뭐 이런느낌인거같은데 템플릿을 왜 쓰는지 정확히 이해가 되지 않았다.

그래서 GPT 에게 다시 물어봤다 왜쓰는지.

 

✅ 템플릿 메서드는 왜 쓰는 걸까?

"전체 알고리즘의 흐름은 고정하되, 일부 단계는 유연하게 바꿀 수 있도록 하기 위해"


🎯 예시로 풀어보자

게임에서 "캐릭터 행동"이라는 공통 흐름이 있다고 해봐요:

1. Idle 상태 진입
2. 이동 처리
3. 타겟 탐색
4. 공격 처리

이 흐름은 모든 캐릭터가 공유하는 로직이에요.
하지만 각각의 캐릭터는 공격 방식이나 타겟 찾는 방식이 다를 수 있죠?

  • 전사는 근거리에서 찌르고
  • 궁수는 원거리에서 조준하고
  • 마법사는 주문을 시전하고

이때 쓰는 게 바로 템플릿 메서드 패턴이에요.


✅ 정리하자면, 템플릿을 쓰는 이유는?

공통 흐름을 재사용하고 싶어서 알고리즘 틀은 매번 새로 만들 필요 없이 재사용 가능
세부 로직만 바꾸고 싶어서 자식 클래스가 필요한 부분만 override 하면 됨
중복 코드를 줄이고 싶어서 부모에 공통 로직을 두면 중복이 줄어듦
확장성 좋고 깔끔한 구조 만들기 위해 새로운 캐릭터, 새로운 동작 추가 시 최소한의 코드만 작성하면 됨

💬 템플릿을 안 쓰면 어떤 문제가 생길까?

class Warrior
{
    public void DoAll()
    {
        Idle();
        Move();
        FindTarget();  // 전용
        Attack();      // 전용
    }
}

class Magician
{
    public void DoAll()
    {
        Idle();
        Move();
        FindTarget();  // 전용
        Attack();      // 전용
    }
}

➡️ 공통 흐름이 중복됨
➡️ 흐름을 바꾸려면 모든 클래스 수정해야 함
➡️ 버그 생기고 유지보수 어려워짐


✅ 템플릿은 이렇게 정리할 수 있어요

템플릿 메서드 패턴은
"전체 알고리즘 흐름은 부모 클래스가 정하고,
그 일부 단계를 자식 클래스가 유연하게 바꿀 수 있도록 만드는 패턴"

 

그러면...그냥 오버 버츄얼로 오버라이드해서 사용하면 되는거 아닌가? 라는 생각이 들었다.

 

“그냥 virtual 메서드로 오버라이드해서 쓰면 되는데,
굳이 템플릿 메서드 패턴을 왜 따로 써야 해?”

✅ 결론부터 말하자면:

단순히 virtual로 메서드 하나하나 오버라이드하는 것과
"템플릿 메서드 패턴"을 적용하는 것의 차이는
알고리즘 전체 흐름을 ‘고정’하고 ‘설계 의도를 명확히 하는가’에 있어요.


🎯 차이점 설명

virtual 단독 오버라이드 각 기능을 개별적으로 바꿔치기만 가능 (공통된 흐름 없음)
템플릿 메서드 패턴 전체 흐름은 부모가 고정하고, 일부 단계만 자식이 커스터마이징

📌 예를 들어보자

① 그냥 virtual 방식

class Player
{
    public virtual void Move() { /* 기본 이동 */ }
    public virtual void Attack() { /* 기본 공격 */ }
}

class Warrior : Player
{
    public override void Move() { /* 워리어 이동 */ }
    public override void Attack() { /* 워리어 공격 */ }
}

➡️ 자식은 알아서 각자 필요한 것만 바꿈
❌ 하지만 **"이 둘을 어떤 순서로 실행해야 하는지"**에 대한 틀이 없음


② 템플릿 메서드 방식

abstract class Player
{
    public void Act()
    {
        Idle();
        Move();
        MoveTarget();
        Attack();
    }

    protected virtual void Idle() { }
    protected virtual void Move() { }
    protected abstract void MoveTarget();
    protected abstract void Attack();
}

➡️ Act()라는 전체 흐름(알고리즘)을 부모가 직접 고정
➡️ 자식은 일부 단계만 정해진 순서에 따라 구현
틀은 지키면서 다양성을 제공


🔥 왜 중요할까?

  • virtual만 쓰면 → 각 메서드가 독립적이라 전체 흐름이 흩어짐
  • 템플릿 패턴 쓰면 → 일관된 흐름 + 유연한 세부 단계 변경 가능
  • 설계 의도를 명확히 할 수 있음
    👉 “이 흐름은 무조건 이렇게 진행돼야 해! 하지만 이 부분은 너가 알아서 해”

✅ 한 줄 정리

그냥 virtual은 부분만 바꾸는 도구,
템플릿 메서드는 흐름을 지키면서 유연하게 바꾸는 설계 도구다.

 

흠.. 그렇다고 한다. 이것도 유니티로 직접 구현해봐야 이해할 수 있을듯 하다. 이론적으로는 알겠다. 구현이 문제군..

반응형
반응형

✅ 클래스 상속 (Inheritance)

✔️ 정의

class A : B처럼, 한 클래스가 다른 클래스의 기능을 물려받는 것

✔️ 목적

  • 코드 재사용: 부모 클래스의 멤버(필드, 메서드 등)를 자식이 그대로 사용 가능
  • 기능 확장: 자식 클래스는 새로운 기능을 추가하거나, 부모 기능을 재정의 가능
  • 다형성(polymorphism): 부모 타입으로 자식 객체를 다룰 수 있음

✔️ 예제

class Character
{
    public virtual void Attack()
    {
        Console.WriteLine("기본 공격!");
    }
}

class Warrior : Character
{
    public override void Attack()
    {
        Console.WriteLine("칼 휘두르기!");
    }
}
Character c = new Warrior();
c.Attack();  // 출력: 칼 휘두르기!

➡️ 부모 타입으로 자식 인스턴스를 다룰 수 있다 = 다형성


⚠️ 단점

  • 단일 상속만 가능: 부모는 한 명만
  • 상속 깊이가 깊어지면 유지보수가 어려워짐
  • 상속 관계가 강한 결합을 만들 수 있음 (부모 변경 시 자식에 영향 큼)

✅ 인터페이스 (Interface)

✔️ 정의

interface IAttackable처럼, 기능의 "약속"만 정의하는 타입
구현 없이 메서드 시그니처만 선언
→ 구현은 클래스에서 직접 해야 함


✔️ 목적

  • 다형성 지원: 특정 인터페이스를 구현한 객체들을 동일하게 다룸
  • 의존성 분리: 구체 타입보다 "역할"에 의존
  • 다중 상속 가능: 하나의 클래스가 여러 인터페이스를 동시에 구현 가능

✔️ 예제

 
interface IAttackable
{
    void Attack();
}

class Archer : IAttackable
{
    public void Attack()
    {
        Console.WriteLine("화살 발사!");
    }
}
IAttackable a = new Archer();
a.Attack();  // 출력: 화살 발사!

➡️ 누구든 IAttackable만 구현하면 같은 방식으로 다룰 수 있음 → 유연함, 약한 결합


✅ 클래스 상속 vs 인터페이스 정리

항목 클래스 상속 인터페이스
목적 기능 물려주기 (재사용) 역할 정의 (규칙 강제)
코드 재사용 ✅ 가능 ❌ 없음 (직접 구현 필요)
다중 상속 ❌ 안 됨 ✅ 가능
구현 제공 ✅ 가능 (virtual, 필드 등) ❌ 없음 (단순 시그니처)
추상화 수준 구체적 (기능 포함) 완전 추상적
설계 유연성 낮음 (강한 결합) 높음 (약한 결합)

그럼 클래스의 abstract와 인터페이스는 좀 비슷한거 아니야? 라고 생각이 들었다. 

 

💯 딱 그거야!
많은 사람들이 처음에 “abstract class랑 interface… 비슷한데 뭐가 달라?” 라고 궁금해해.
왜냐면 둘 다 “강제 구현”을 요구하고, 직접 인스턴스를 만들 수 없고, 자식이 override 해야 하니까 헷갈릴 수 있어.

근데! 실제로는 의도와 사용 목적이 분명하게 다르기 때문에


**"언제 abstract class를 쓰고 언제 interface를 쓰는가?"**가 중요한 핵심이야.

✅ 공통점

인스턴스 생성 불가 abstract class, interface 모두 직접 new로 못 만듦
메서드 강제 구현 자식 클래스가 반드시 override해야 하는 메서드 있음
다형성 지원 부모 타입으로 자식 인스턴스를 다룰 수 있음 (Animal a = new Dog())

⚔️ 차이점

항목 abstract class interface
목적 공통 로직 공유 + 일부 구현 강제 **기능 명세(역할)**만 정의
기본 구현 제공 ✅ 가능 (필드, 메서드, 속성 등) ❌ 원칙상 불가능 (C# 8부터 일부 가능하긴 함)
필드 선언 ✅ 가능 ❌ 불가능
생성자 ✅ 가질 수 있음 ❌ 없음
상속 개수 ❌ 단일 상속만 가능 ✅ 다중 상속 가능
접근 제한자 ✅ 사용 가능 (protected, private, 등) ❌ 기본적으로 public only

🎮 메이플스토리 비유로 다시 정리!

abstract class 공통 기능 + 각 직업 전용 스킬: 기본 체력 시스템, 레벨업 시스템 등 공유됨 + 각 전직 클래스가 고유 스킬 구현
interface 공격 가능한 존재: 플레이어, 몬스터, 보스가 각각 다른 방식으로 Attack() 구현함 → 역할만 공유함

✅ 언제 abstract class를 쓰고 언제 interface를 써?

공통 로직도 공유해야 함 ✅ abstract class
여러 클래스가 같은 기능을 수행하지만, 내부 로직은 각자 다르게 하고 싶을 때 ✅ interface
여러 개의 기능(역할)을 동시에 구현해야 함 ✅ interface
기본 멤버/필드/생성자도 물려줘야 함 ✅ abstract class

🧠 기억하기 좋은 한 줄 요약

interface는 “무엇을 할 수 있는가”를 말하고,
abstract class는 “무엇을 공유할 것인가”를 말한다.

 

흠.. 클래스 상속과 인터페이스는 알겠는데 아직까지 솔직히 abstract Class와 Interface의 차이가 이론적으로는 알겠지만 마음으로 와닿지 않는다 이건 직접 유니티로 구현해보면서 경험을 쌓는 수 밖에 없을 듯 하다.. 

반응형

'C#' 카테고리의 다른 글

C# - Parent obj = new Child(); 메모리는 어떻게 될까? & 다형성  (1) 2025.07.03
C# - base 키워드  (3) 2025.07.03
C# - Virtual & abstract  (0) 2025.07.03
C# - 자료구조  (1) 2025.07.02
C# - 해쉬셋(HashSet) vs Dictionary(HashTable)  (2) 2025.07.02

C# - Virtual & abstract

Dev_Jen
|2025. 7. 3. 10:04
반응형

✅ virtual 키워드 정리

📌 정의

virtual은 부모 클래스에서 메서드나 속성을 "재정의 가능"하게 만드는 키워드입니다.


📋 기본 문법

class Parent
{
    public virtual void SayHello()
    {
        Console.WriteLine("안녕하세요. 부모입니다.");
    }
}

class Child : Parent
{
    public override void SayHello()
    {
        Console.WriteLine("안녕하세요. 자식입니다.");
    }
}

✅ 주요 특징

기본 구현 제공 부모 클래스가 기본 로직을 정의
자식 클래스에서 변경 가능 override 키워드로 오버라이딩 가능
선택적 재정의 자식 클래스에서 반드시 재정의하지 않아도 됨
다형성 지원 부모 타입으로 자식 객체를 가리킬 때, 실제 자식의 메서드가 실행됨

🔍 예시

 
Parent obj1 = new Parent();
obj1.SayHello();  // 출력: 안녕하세요. 부모입니다.

Child obj2 = new Child();
obj2.SayHello();  // 출력: 안녕하세요. 자식입니다.

Parent obj3 = new Child();
obj3.SayHello();  // 출력: 안녕하세요. 자식입니다. (다형성!)

🧠 언제 쓸까?

  • 기본 동작은 있으나, 자식 클래스에서 상황에 따라 동작을 바꾸고 싶을 때
  • 템플릿 메서드 패턴에서 일부 단계를 virtual로 두면 자식 클래스가 필요 시 바꿀 수 있음

❗ 주의

  • virtual 메서드는 자식 클래스에서 override 키워드로 재정의해야
  • virtual 없이 그냥 override는 불가능함 → 부모가 virtual 또는 abstract여야 함

 

그럼 궁금한게 꼭 버츄얼을 써야 오버라이드가 가능한건가? 싶기도하고 abstract는 또 뭘까 싶었다.

 

✅ 1. 꼭 virtual을 써야 override가 가능한가?

👉 네, 맞습니다.

  • override 키워드는 반드시 부모 클래스에서 virtual, abstract, 또는 override로 선언된 메서드만 재정의할 수 있어요.
  • 즉, virtual 없이 그냥 만든 메서드는 자식 클래스에서 override 할 수 없습니다.
class Parent
{
    public void Speak()  // virtual이 아님
    {
        Console.WriteLine("부모가 말합니다");
    }
}

class Child : Parent
{
    // ❌ 오류! override 불가능
    public override void Speak()
    {
        Console.WriteLine("자식이 말합니다");
    }
}

✅ 2. 그럼 abstract는 뭐야?

👉 abstract는 부모 클래스에서 "이건 반드시 자식 클래스가 직접 구현해야 해!" 라고 강제할 때 사용해요.

📌 특징 비교

  

구분 virtual abstract
기본 구현 ✅ 있음 ❌ 없음 (구현 금지)
재정의 여부 자식 클래스에서 재정의 선택 가능 자식 클래스에서 반드시 override 해야 함
용도 "기본 구현은 있지만, 필요하면 바꿔 써도 돼" "이건 자식이 반드시 구현해야 돼"

 


🎯 코드 예시로 완전 비교

🔹 virtual 예시

class Parent
{
    public virtual void Greet()
    {
        Console.WriteLine("Hello from Parent");
    }
}

class Child : Parent
{
    public override void Greet()
    {
        Console.WriteLine("Hello from Child");
    }
}
 

🔹 abstract 예시

abstract class Animal
{
    public abstract void Speak();  // 구현 ❌
}

class Dog : Animal
{
    public override void Speak()
    {
        Console.WriteLine("멍멍!");
    }
}

→ Speak()는 무조건 자식이 override 해야 함.
→ 안 하면 컴파일 오류 발생


🧠 요약 정리

질문답변
virtual 없이 override 가능? ❌ 불가능 (virtual, abstract, 또는 override 필요)
abstract는 뭐야? 자식 클래스가 반드시 구현해야 하는 메서드를 정의할 때 사용
virtual과 abstract 차이는? 기본 구현 유무 + 오버라이드 강제 여부의 차이

그러면 굳이 abstract를 써야하는 이유가있나?..싶었다..

그럼 도대체 두개의 차이점이 뭘까..

 

✅ 핵심 요약: 둘의 의도와 차이

 

항목 virtual abstract
기본 구현 있다 → 자식이 그대로 써도 됨 없다 → 자식이 반드시 구현해야 함
자식에서 override 선택 가능 강제
설계 의도 "기본 동작이 있지만, 필요하면 바꿔 써" "이건 네가 알아서 반드시 만들어"
목적 유연성 강제성, 명확한 계약
사용 가능 클래스 일반 클래스, 추상 클래스 추상 클래스에서만 사용 가능
장점 코드 재사용 가능 설계 실수 방지, 역할 명확
단점 재정의 안 하면 예측 어려움 반복적 구현 필요 (유지비 증가)
 

🎯 예시 비교로 감 잡기

✅ 예제 상황: "게임 캐릭터"

1. virtual 예시

class Character
{
    public virtual void Attack()
    {
        Console.WriteLine("기본 공격: 펀치!");
    }
}

class Knight : Character
{
    public override void Attack()
    {
        Console.WriteLine("검으로 공격!");
    }
}

class Villager : Character
{
    // Attack() 안 바꿔도 됨 → 기본 공격 그대로 사용
}

➡️ Villager는 특별한 공격이 없으니 virtual 덕분에 기본 공격을 그대로 써도 괜찮음
➡️ 유연함이 중요할 때 → virtual


2. abstract 예시

abstract class Character
{
    public abstract void Attack();  // 반드시 구현!
}

class Knight : Character
{
    public override void Attack()
    {
        Console.WriteLine("검으로 공격!");
    }
}

class Villager : Character
{
    // ❌ Attack() 안 만들면 컴파일 에러 → 강제
}

➡️ 모든 캐릭터는 반드시 자신만의 공격을 가져야 해! → 이런 명확한 룰을 강제할 때 abstract
➡️ 설계 실수 방지하고 싶을 때 → abstract


📌 그럼 뭐가 더 효율적인가?

👉 "효율"은 상황에 따라 달라요.

기본 동작은 있지만, 바꿀 수 있도록 열어두고 싶다 ✅ virtual
꼭 자식이 직접 구현해야만 의미가 있다 ✅ abstract
공통 로직은 부모에서 쓰고, 일부만 다르게 처리하고 싶다 ✅ 템플릿 메서드 + abstract + virtual 혼용
 

🧠 결론

  • virtual → "기본은 줄게, 필요하면 바꿔 써"
    → 재사용 중심, 유연한 구조
  • abstract → "이건 네가 반드시 해"
    → 계약 기반 설계, 실수 방지, 역할 명확화

🎯 기억하기 쉽게 요약하면:

"바꿔도 되고 안 바꿔도 되면 virtual,
무조건 바꿔야 한다면 abstract"

 

음...정확하게 뭔가 와닿지는 않지만 캐릭터의 스킬이나 공격스킬로 따지자면

virtual - 기본 공격 스타일 메이플의 기본공격정도이지 않을까 싶다. 기본적으로 찌르기,내려치기,올려치기 등등 기술을 사용할때 정도?

abstract - 메이플에서의 전사,궁수,해적,마법사,도적 등등 전직했을때의 각각의 스킬로 사용할때정도로 정의되지 않을까 싶다!!  

 

🔥 비유 리뷰

virtual 기본 공격 스타일 (찌르기, 내려치기 등) "기본은 이거야, 하지만 바꿔도 돼" → 재정의 가능, 선택적
abstract 전직 클래스별 고유 스킬 (전사, 궁수, 해적 등) "반드시 직접 구현해야 해" → 자식 클래스가 구현 강제됨
 

→ 이 비유는 진짜로 설계 철학에 딱 맞아.

라고 GPT가 친절하게 설명해주네여ㅎㅎ

반응형
반응형

오늘 하루 계획

  • 오전
    • 09:00 ~ 13:00 : CS 공부
      • 어제 내용 복습 - GC, 해시테이블, 딕셔너리, .NET 런타임, 캡슐화, 박싱/언박싱 & 형변환
      • HashTable
      • Dictionary
      • HashSet
      • 제네릭 (Generics)
      • ScriptableObject
  • 오후
    • 13:00 ~ 14:00 : 점심시간
    • 14:00 ~ 18:00 : 팀 프로젝트 진행 (12조)
      • 폭탄 스킬 기능 구현 및 사운드 연결
      • 카드 수량에 따라 3x3 / 4x4 자동 배치 처리
      • 게임오버 사운드 추가
      • GameManager, Card, Board 등 스크립트 리팩토링
      • ScriptableObject Stage 데이터 기반 동작 적용 완료
    • 18:00 ~ 19:00 : 저녁시간
  • 저녁
    • 19:00 ~ 20:00 : 팀 회의
    • 20:00 ~ 20:30 : 개인 TIL 작성
    • 20:30 ~ 21:00 : 팀원 회고

1. 오늘 학습 키워드

  • HashTable
  • Dictionary
  • HashSet
  • 제네릭 (Generics)
  • ScriptableObject
  • 카드 자동 배치 (3x3, 4x4)
  • 폭탄 스킬
  • 사운드 연결
  • GameManager 구조 개선

2. 오늘 학습 한 내용을 나만의 언어로 정리하기

https://dev-jen.tistory.com/entry/%ED%95%B4%EC%8B%9C%ED%85%8C%EC%9D%B4%EB%B8%94HashTable-vs-%EB%94%95%EC%85%94%EB%84%88%EB%A6%ACDictionary%EC%A0%9C%EB%84%A4%EB%A6%ADGeneric

 

C# - 해시테이블(HashTable) vs 딕셔너리(Dictionary)&제네릭(Generic)

✅ 해시테이블 (Hash Table)📌 개념키(key)를 해시 함수(Hash Function)에 넣어 나온 해시값을 기반으로 데이터를 저장하는 자료구조.내부적으로는 배열(array)와 연결 리스트 또는 트리 구조를 함께 사용

dev-jen.tistory.com

https://dev-jen.tistory.com/entry/Unity-%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%84%B0%EB%B8%94%EC%98%A4%EB%B8%8C%EC%A0%9D%ED%8A%B8ScriptableObject%EC%98%88%EC%A0%9C

 

Unity - 스크립터블오브젝트(ScriptableObject)&예제

✅ ScriptableObject란?📌 개념 정리ScriptableObject는 MonoBehaviour처럼 Unity에서 사용하는 클래스지만,씬에 붙는 컴포넌트가 아니고, 독립적인 데이터 객체야.데이터를 자산(Asset)으로 만들어 저장하고 공

dev-jen.tistory.com

https://dev-jen.tistory.com/entry/C-%ED%95%B4%EC%89%AC%EC%85%8BHashSet-vs-DictionaryHashTable

 

C# - 해쉬셋(HashSet) vs Dictionary(HashTable)

✅ HashSet이란?중복을 허용하지 않는 데이터 집합 (Set)을 구현한 자료구조내부적으로는 **해시 테이블(Hash Table)**을 사용하지만,핵심 목적은 **"중복 없는 값의 모음"**을 만드는 거야.🔍 HashSet의

dev-jen.tistory.com

https://dev-jen.tistory.com/entry/C-%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0

 

C# - 자료구조

✅ 자료구조란?자료(데이터)를 효율적으로 저장하고, 관리하고, 사용할 수 있도록 구성한 방식쉽게 말하면,"데이터를 어떻게 정리해서 넣고, 꺼내고, 수정할지에 대한 규칙과 구조"✅ 왜 중요한

dev-jen.tistory.com

 

오늘은 자료구조의 기초부터 Unity의 실전 기능까지 폭넓게 다뤘다.

먼저 CS 공부 시간에는 해시 기반 자료구조들인 HashTable, Dictionary, HashSet의 차이를 살펴보았고,

C# 제네릭(Generic)의 개념과 장점을 학습하면서 타입 안정성과 재사용성의 중요성을 이해했다.

Unity에서는 ScriptableObject를 활용해 레벨(Stage) 정보를 분리하고, ScriptableObject로 예제까지 만들어보았다.

이를 바탕으로 카드 수량에 따라 3x3 또는 4x4로 자동 배치되도록 구현했다.

또한 폭탄 아이템 기능을 추가하고, 터질 때 사운드까지 연동했다.

게임오버 시 효과음도 추가하였고, 전반적인 스크립트 구조 (GameManager, Card, Board)를 정리했다.


3. 학습하며 겪었던 문제점 & 에러

1. ScriptableObject에서 배열 값이 null로 나오는 문제

  • 문제정의: Stage ScriptableObject에서 cardCount 배열이 정상적으로 세팅돼 있음에도 불구하고 Board.cs에서 NullReferenceException 발생.
  • 시도한 방법: StageManager가 정상적으로 초기화됐는지, ScriptableObject가 Inspector에 잘 연결되어 있는지 확인.
  • 해결 방법: StageManager.Instance.GetCurrentStage()를 사용하는 시점에서 Awake()보다 Start() 호출이 늦게 되어 참조가 실패하고 있었음. Stage 데이터를 직접 연결하거나 Board에서도 Start()에서 안전하게 참조.
  • 새롭게 알게 된 점: ScriptableObject는 인스턴스화되어 있어도 호출 시점에 따라 null 오류가 발생할 수 있음.
  • 다시 만나게 된다면: null 체크를 철저히 하고, ScriptableObject 데이터 접근은 Awake()보다 늦은 Start()에서 처리.

2. 카드 배치가 4x4가 아닌 3xN처럼 나오는 문제

  • 문제정의: 카드 수량이 16개임에도 Board.cs에서 카드가 가로 3개로만 배치되는 현상.
  • 시도한 방법: (i % 4) 및 (i / 4) 수식을 확인했으나 문제없었음.
  • 해결 방법: ScriptableObject 에서 받아오는 if문의 Level값을 내가 잘못 설정했음..
  • 새롭게 알게 된 점: 숫자를 잘 보자..
  • 다시 만나게 된다면: 하나하나 천천히 다시 체크하는 습관을 들이자.

3. 폭탄 사운드가 들리지 않거나 Destroy 오류 발생

  • 문제정의: 폭탄 프리팹 생성 시 사운드가 출력되지 않거나 "Destroying assets is not permitted" 오류 발생.
  • 시도한 방법: Instantiate(bombPrefab) 후 Destroy(bombPrefab, 2f) 사용.
  • 해결 방법: bombPrefab은 에셋이고 인스턴스가 아니므로 Destroy() 대상이 잘못됨. GameObject go = Instantiate(bombPrefab); 후 Destroy(go, 2f);로 변경하여 해결.
  • 새롭게 알게 된 점: Instantiate() 후 Destroy()는 반드시 인스턴스를 대상으로 해야 한다. 에셋 리소스를 직접 파괴하려 하면 Unity가 경고를 출력함.
  • 다시 만나게 된다면: 항상 Instantiate() 결과를 변수로 받아 Destroy()에 넘기기.

4. 메모

ScriptableObject 간단하게 구현 Gif

 

반응형

C# - 자료구조

Dev_Jen
|2025. 7. 2. 12:13
반응형

✅ 자료구조란?

자료(데이터)를 효율적으로 저장하고, 관리하고, 사용할 수 있도록 구성한 방식

쉽게 말하면,

"데이터를 어떻게 정리해서 넣고, 꺼내고, 수정할지에 대한 규칙과 구조"


✅ 왜 중요한가?

  • 빠르게 찾기 위해
  • 효율적으로 저장하기 위해
  • 복잡한 데이터를 쉽게 다루기 위해
  • 성능 좋은 알고리즘을 만들기 위해

✅ 비유로 설명하면

  • 자료구조 = 서랍장
    예를 들어, 옷을 아무데나 넣으면 나중에 찾기 힘들지?
    자료구조는 "양말은 맨 위 칸", "바지는 아래 칸"처럼 정리 기준을 정해주는 거야.

✅ 주요 자료구조 종류 (기초)

자료구조설명예시
배열 (Array) 순서대로 데이터를 저장 리스트, 숫자 모음
리스트 (List) 동적으로 크기가 변하는 배열 C#의 List<T>
스택 (Stack) 나중에 넣은 게 먼저 나옴 (LIFO) 웹 브라우저 뒤로가기
큐 (Queue) 먼저 넣은 게 먼저 나옴 (FIFO) 줄서기, 작업 대기열
해시테이블 (Hash Table) 키로 값 빠르게 찾기 전화번호부
트리 (Tree) 계층적 데이터 구조 폴더 구조, 게임 스킬트리
그래프 (Graph) 노드 간 연결 표현 길찾기, SNS 관계망
집합 (Set) 중복 없는 데이터 모음 HashSet
 

✅ 프로그래머 입장에서 왜 중요할까?

  • 자료구조에 따라 코드 성능이 완전히 달라짐
  • 빠르게 검색, 정렬, 삽입하려면 적절한 자료구조 선택이 중요
  • 코딩테스트, 실무 모두 자료구조가 기초체력임

✅ 결론

자료구조는 "데이터를 효율적으로 다루기 위한 도구상자"
잘 쓰면 빠르고, 못 쓰면 느리고 버그 많아져.

 

흠..트리랑 그래프빼고는 다 아는데 트리랑 그래프에 대해서도 공부해봐야겠다!

반응형
반응형

✅ HashSet이란?

중복을 허용하지 않는 데이터 집합 (Set)을 구현한 자료구조
내부적으로는 **해시 테이블(Hash Table)**을 사용하지만,
핵심 목적은 **"중복 없는 값의 모음"**을 만드는 거야.


🔍 HashSet의 특징

❌ 중복 허용 안 함 같은 값을 두 번 넣어도 한 번만 저장됨
🔍 순서 없음 넣은 순서 보장 안 됨
⚡ 매우 빠름 추가, 삭제, 탐색 전부 평균 O(1)
📦 내부 구조 키만 있고, 값은 없음 (딕셔너리는 key-value, 얘는 key만 존재)

✅ HashSet 예제 (C#)

using System.Collections.Generic;

HashSet<string> names = new HashSet<string>();

names.Add("Alice");
names.Add("Bob");
names.Add("Alice");  // 중복! 무시됨

Console.WriteLine(names.Count); // 출력: 2
Console.WriteLine(names.Contains("Bob")); // true

✅ HashSet vs Dictionary (or Hashtable)

키와 값 키만 존재 키와 값 존재
목적 중복 없는 목록 키로 값을 매핑
내부 구현 Hash Table 기반 Hash Table 기반
사용 예 방문 여부 확인, 유니크 목록 설정값 저장, 데이터 검색

✅ 언제 쓰나?

중복 제거 중복 있는 리스트에서 유니크한 값만 뽑을 때
빠른 포함 여부 확인 어떤 값이 존재하는지 빠르게 검사할 때
태그/이름/고유값 저장 "이미 처리했는가?"를 추적할 때

✅ 예: 방문한 적 있는 위치 추적

HashSet<Vector2Int> visited = new HashSet<Vector2Int>();

Vector2Int current = new Vector2Int(2, 3);

if (!visited.Contains(current))
{
    visited.Add(current);
    // 처음 방문한 위치 처리
}

📌 핵심 요약

HashSet<T>는 딕셔너리에서 값(value)을 없앤 버전이라고 생각하면 쉬워.
딕셔너리: key → value
해시셋: key만 저장 (중복 없음)

 

오 해쉬셋과 딕셔너리가 둘다 해시테이블 기반으로 만들어졌다는게 좀 신기했다. 딕셔너리는 해시테이블의 상위호환이지 해시테이블 기반인줄은 몰랐다. 다시 GPT에게 물어보니 해시테이블 기반이 맞다고 한다.

또 궁금한게 생겼다. 딕셔너리에서는 값을 없앤 버전이라고 언급하는데 그럼 딕셔너리는 중복이 허용 가능한가? 싶었다 다시 GPT에게 물어보니

 

딕셔너리(Dictionary)는 키(key) 중복을 허용하지 않아.
✅ 단, 값(value) 은 중복될 수 있어.


✅ 딕셔너리에서 중복이 되는 부분과 안 되는 부분

항목중복 가능 여부설명
Key ❌ 불가능 같은 키로 두 번 Add() 하면 예외 발생
Value ✅ 가능 여러 키가 같은 값을 가질 수 있음

✅ 예시로 확인해보자

 
Dictionary<string, int> dict = new Dictionary<string, int>();

dict.Add("apple", 1);
dict.Add("banana", 1);   // value는 중복 가능
dict.Add("apple", 2);    // ❌ 오류! 키 중복
System.ArgumentException: 항목이 이미 추가되었습니다.

 

즉,

  • "apple"이라는 키가 두 번 들어가는 건 안됨
  • 값인 1이 두 번 들어가는 건 가능함

✅ 해시셋은?

  • 아예 Key = Value인 구조야.
  • 따라서 모든 값 자체가 고유해야 함
  • 즉, "apple"을 두 번 넣으면 무시됨.

🧠 핵심 요약

자료구조중복 가능 여부
Dictionary ❌ 키는 중복 불가 / ✅ 값은 중복 가능
HashSet ❌ 값 중복 불가 (값 = 키)

음..이렇게 된다면 해시테이블도 궁금해졌다.

✅ Hashtable도 Dictionary와 마찬가지로

항목중복 가능 여부
Key ❌ 중복 불가
Value ✅ 중복 가능

 

확실히 딕셔너리도 해시테이블 기반으로 만들어진거다 보니 키값은 둘다 똑같이 중복이 안된다고한다.

정리하자면

HashSet - 키값만 있음, 키값 중복안됨. 약간 데이터 저장용도일까...정보를 넣었는지 안넣었는지 구별할때나 있는지 없는지 구별할떄 많이 사용할 듯 하다.

Dictionary - 키값, 값 둘다있음. 하지만 키값은 중복안됨.

HashTable - 키값, 값 둘다음. 하지만 키값은 중복안됨.

여기서봤을떄는 딕셔너리와 해시테이블이 같아 보이지만. 명확하게 차이가 나는걸 알고있다.

이렇게 계속 비교하면서 공부하는게 참 재밌는거같다

https://dev-jen.tistory.com/entry/%ED%95%B4%EC%8B%9C%ED%85%8C%EC%9D%B4%EB%B8%94HashTable-vs-%EB%94%95%EC%85%94%EB%84%88%EB%A6%ACDictionary%EC%A0%9C%EB%84%A4%EB%A6%ADGeneric

 

C# - 해시테이블(HashTable) vs 딕셔너리(Dictionary)&제네릭(Generic)

✅ 해시테이블 (Hash Table)📌 개념키(key)를 해시 함수(Hash Function)에 넣어 나온 해시값을 기반으로 데이터를 저장하는 자료구조.내부적으로는 배열(array)와 연결 리스트 또는 트리 구조를 함께 사용

dev-jen.tistory.com

 

반응형

'C#' 카테고리의 다른 글

C# - Virtual & abstract  (0) 2025.07.03
C# - 자료구조  (1) 2025.07.02
C# - 해시테이블(HashTable) vs 딕셔너리(Dictionary)&제네릭(Generic)  (0) 2025.07.02
C# - .NET Runtime  (1) 2025.07.01
C# - 캡슐화  (1) 2025.07.01