no image
내일배움캠프 8일차 TIL [C# 고급 문법 정리 + 텍스트 RPG 제작 시작]
🗓️ 오늘 하루 일정✅ 오전09:00 ~ 11:30 : 개인 복습 및 블랙잭 게임 완성콘솔 블랙잭 게임 예제 제작 및 실습C# 문법 예제들 분석인터페이스와 열거형예외 처리값형과 참조형 비교박싱(Boxing) / 언박싱(Unboxing)델리게이트와 람다식Nullable 타입과 null 조건 연산자(?.)StringBuilder 사용법11:30 ~ 13:00 : C# 체크리스트 강의 Day2 수강연산자 및 프로그래머스 실습 문제 풀이Math.Pow, Math.Sqrt, Math.Log10 등 수학 함수 사용 학습🍽️ 점심시간13:00 ~ 14:00 : 점심시간✅ 오후14:00 ~ 18:00 : 텍스트 RPG 프로젝트 개발Program 클래스Main() 메서드에서 게임 전체 흐름 시작StartGame()을..
2025.07.09
no image
C# - StringBuilder - 문자열 성능 최적화
🧱 StringBuilder - 문자열 성능 최적화의 열쇠C#에서 문자열을 다룰 때 가장 흔하게 쓰는 건 string 타입이다.그런데 문자열을 반복해서 붙이고 수정할 일이 많아지면,string을 계속 쓰는 건 성능적으로 비효율적이다!이때 등장하는 게 바로 StringBuilder다.✅ string과 StringBuilder의 차이점구분stringStringBuilder불변(Immutable)✅ O❌ X변경 시새로운 문자열 생성내부 버퍼 수정성능느림 (많이 붙이면 메모리 낭비)빠름 (가변 구조)용도문자열 1~2회 조작반복적 조작 (루프, 누적 등) ✅ StringBuilder 기본 사용법using System.Text;StringBuilder sb = new StringBuilder();sb.Append..
2025.07.09
C#
no image
C# - Nullable 타입과 null 조건 연산자
❓ C# Nullable 타입과 null 조건 연산자 완전 정복프로그래밍을 하다 보면 "값이 없을 수도 있는 상황"이 자주 발생한다.예를 들어, 점수가 없을 수도 있고, 데이터가 존재하지 않을 수도 있다.이럴 때 무작정 값을 쓰려고 하면 오류가 발생할 수 있는데,이걸 안전하게 처리하는 방법이 바로 Nullable 형식과 null 조건 연산자다!✅ Nullable 형식 (?)값 타입에도 null을 허용하도록 해주는 문법int? score = null; // OK!int score = null; // 오류! 기본 int는 null 불가int, float, bool 같은 값 타입은 기본적으로 null을 가질 수 없음int?, bool?처럼 ?를 붙이면 null을 담을 수 있는 형식이 된다🔎 사용 예..
2025.07.09
C#
no image
C# - 델리게이트와 람다, 함수도 변수처럼! + LINQ
🎯 델리게이트와 람다 - 함수도 변수처럼 다룰 수 있다?!이번엔 드디어 **델리게이트(delegate)**와 람다(lambda) 개념에 대해 배웠다.처음엔 "함수를 변수처럼 넘긴다고?" 싶어서 어려웠는데,직접 써보니까 유연한 코드 설계를 가능하게 해주는 정말 강력한 기능이라는 걸 느꼈다.✅ 델리게이트(Delegate)란?메서드를 참조할 수 있는 변수함수를 변수처럼 저장하거나, 메서드를 다른 메서드에 전달하고 싶을 때 사용된다.delegate int Calculate(int x, int y); // 델리게이트 선언int Add(int a, int b) => a + b;Calculate calc = Add; // 메서드 참조Console.WriteLine(calc(3, 5)); // 8 출력delegate..
2025.07.09
C#
no image
C# - 값형(Value Type)과 참조형(Reference Type) 그리고 박싱(Boxing) & 언박싱(Unboxing)
📦 값형과 참조형, 그리고 박싱과 언박싱 완전 정복하기!이번엔 C#을 공부하면서 헷갈리기 쉬운 개념 중 하나인**값형(Value Type)**과 참조형(Reference Type), 그리고그 사이에서 왔다갔다 하는 박싱(Boxing)과 언박싱(Unboxing) 개념을 정리해봤다.처음에는 그냥 값이냐 참조냐 하고 넘겼는데,실제로 메모리에 어떻게 저장되고 전달되는지를 이해하니까왜 중요한지 확실히 느껴졌다!✅ 값형(Value Type)이란?값을 "그 자체"로 저장하는 타입변수에 값이 직접 저장됨다른 변수에 복사할 때 값이 그대로 복사됨대표적인 예: int, float, bool, char, struct 등 int a = 10;int b = a;b = 20;Console.WriteLine(a); // 10a와..
2025.07.09
C#
no image
C# - 예외처리(Exception Handling)
✅ 예외 처리(Exception Handling) - 프로그램을 안전하게 지키는 방패C#을 비롯한 대부분의 언어에서는 **예외(Exception)**라는 개념이 있다.예외는 프로그램 실행 중에 발생하는 예상하지 못한 상황을 의미하고,이걸 제대로 처리하지 않으면 프로그램이 갑자기 종료될 수 있다.그렇기 때문에 예외는 단순한 에러가 아니라,예외 상황에 대응하는 코드를 반드시 작성해줘야 한다.📌 예외란 무엇인가요?예외는 다음과 같은 상황에서 발생할 수 있다:0으로 나누기존재하지 않는 파일 열기배열 인덱스 초과 접근null 객체 사용 등등이런 예외가 발생하면 C#에서는 프로그램을 중단하고 예외 메시지를 출력한다.하지만 우리가 try-catch 문을 사용하면 예외를 잡아서 직접 처리할 수 있다.🔧 기본 구조:..
2025.07.09
C#
no image
C# - 인터페이스와 열거형 (Interface, enum)
✅ 인터페이스란 무엇인가요?객체지향 프로그래밍을 공부하면서 인터페이스는 정말 많이 등장하는 개념이다.클래스와 비슷해 보이지만, 사용하는 목적이 분명히 다르다.이번 글에서는 인터페이스의 개념부터 왜 필요한지, 어떻게 사용하는지까지예제와 함께 하나씩 정리해보려고 한다.📌 다중 상속이 왜 위험한가요?C#은 클래스 간의 다중 상속을 허용하지 않는다.그 이유는 대표적으로 아래와 같은 문제가 있기 때문이다.🧱 1. 다이아몬드 문제(Diamond Problem)A → B, A → C, B와 C → D 를 상속받으면A의 멤버가 D에 중복 상속되어 어떤 걸 써야 하는지 모호해진다.이런 모호성을 해결하려다 보면 코드가 더러워진다 😵🔀 2. 복잡한 상속 구조여러 클래스를 동시에 상속받으면 클래스 간 관계가 꼬인다.디..
2025.07.09
C#
no image
C# - 콘솔 블랙잭 게임 & 사용되는 예제들
1. 게임 기획게임 목표 정의 (21점을 넘지 않으면서 딜러보다 높은 점수 획득)플레이어 수 (1인 vs 딜러)승패 조건 정의카드 규칙 요약 (A=1 또는 11, J/Q/K=10, 숫자카드=해당 숫자)2. 기본 구조 설계클래스 설계Card (문양, 숫자)Deck (카드 덱, 섞기, 카드 뽑기)Player (카드 리스트, 점수 계산)GameManager (게임 흐름 제어)Enum 설계Suit (Hearts, Spades, Clubs, Diamonds)Rank (Ace ~ King)3. 게임 로직 구현카드 덱 생성 및 셔플플레이어/딜러에게 카드 2장씩 배분점수 계산 로직 (A의 처리 포함)플레이어 선택 (Hit / Stand)딜러 동작 (점수 17 이상일 때까지 Hit)승패 판정 및 출력4. 입출력 처리콘솔..
2025.07.09
C#
반응형

🗓️ 오늘 하루 일정

✅ 오전

  • 09:00 ~ 11:30 : 개인 복습 및 블랙잭 게임 완성
    • 콘솔 블랙잭 게임 예제 제작 및 실습
    • C# 문법 예제들 분석
    • 인터페이스와 열거형
    • 예외 처리
    • 값형과 참조형 비교
    • 박싱(Boxing) / 언박싱(Unboxing)
    • 델리게이트와 람다식
    • Nullable 타입과 null 조건 연산자(?.)
    • StringBuilder 사용법
  • 11:30 ~ 13:00 : C# 체크리스트 강의 Day2 수강
    • 연산자 및 프로그래머스 실습 문제 풀이
    • Math.Pow, Math.Sqrt, Math.Log10 등 수학 함수 사용 학습

🍽️ 점심시간

  • 13:00 ~ 14:00 : 점심시간

✅ 오후

  • 14:00 ~ 18:00 : 텍스트 RPG 프로젝트 개발
    1. Program 클래스
      • Main() 메서드에서 게임 전체 흐름 시작
      • StartGame()을 통해 시작화면 출력 및 Scene 이동 처리
      • 전역 객체 관리: player, inventory, shop
    2. Player 클래스
      • 플레이어의 상태 정보 보유: Name, Job, Level, HP, Attack, Defense, Gold
      • PlayerInfo()로 상태 출력 (👉 오늘 꾸민 UI 포함)
    3. Item 클래스
      • 아이템 속성 보유: Name, Info, Price, StatValue, ItemType, IsEquipped
    4. Inventory 클래스
      • 아이템 보관 및 출력: Items 리스트
      • AddItem(), ShowInventory(), ManageEquip() 메서드로 인벤토리 관리
      • 오늘 꾸민 인벤토리 UI (장착 여부, 설명, 종류 표시 등)
    5. Shop 클래스
      • 상점 아이템 리스트 보유 및 초기화: Items, InitializeItems()
      • ShowShopMenu()를 통해 상점 기능 선택
      • BuyItem()과 SellItem() 구현, 아이템 출력 정렬 + 색상 출력 등 UI 개선
    6. GameSystem 클래스 (static 유틸리티)
      • 메시지 색상 출력 기능 모듈화
      • 예: StringPrintRed(), StringPrintGreen(), FaileInput()

🍽️ 저녁시간

  • 18:00 ~ 19:00 : 저녁식사 및 휴식

✅ 저녁 이후

  • 19:00 ~ 21:00 : 텍스트 RPG 마무리 정리
    • 플레이어 상태 보기 꾸미기
    • 상점 메뉴 입장 오류 디버깅
    • 인벤토리 장착 관리 switch-case 보완

✅ 오늘 학습 키워드

  • 콘솔 블랙잭 게임 구현 실습
  • C# 인터페이스 (interface)
  • 열거형 (enum)
  • 예외 처리 (try-catch)
  • 값 형식과 참조 형식
  • 박싱(Boxing)과 언박싱(Unboxing)
  • 델리게이트(delegate)와 람다(lambda)
  • Nullable 타입 & null 조건 연산자 (?.)
  • StringBuilder 클래스
  • 삼항 연산자(? :) 활용
  • Console UI 색상 꾸미기
  • 인벤토리/상점 시스템 구현
  • 텍스트 RPG 시스템 개발 (Player, Item, Inventory, Shop 클래스 설계)

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

 

C# - 콘솔 블랙잭 게임 & 사용되는 예제들

1. 게임 기획게임 목표 정의 (21점을 넘지 않으면서 딜러보다 높은 점수 획득)플레이어 수 (1인 vs 딜러)승패 조건 정의카드 규칙 요약 (A=1 또는 11, J/Q/K=10, 숫자카드=해당 숫자)2. 기본 구조 설계클

dev-jen.tistory.com

 

C# - 인터페이스와 열거형 (Interface, enum)

✅ 인터페이스란 무엇인가요?객체지향 프로그래밍을 공부하면서 인터페이스는 정말 많이 등장하는 개념이다.클래스와 비슷해 보이지만, 사용하는 목적이 분명히 다르다.이번 글에서는 인터페

dev-jen.tistory.com

 

C# - 예외처리(Exception Handling)

✅ 예외 처리(Exception Handling) - 프로그램을 안전하게 지키는 방패C#을 비롯한 대부분의 언어에서는 **예외(Exception)**라는 개념이 있다.예외는 프로그램 실행 중에 발생하는 예상하지 못한 상황을

dev-jen.tistory.com

 

C# - 값형(Value Type)과 참조형(Reference Type) 그리고 박싱(Boxing) & 언박싱(Unboxing)

📦 값형과 참조형, 그리고 박싱과 언박싱 완전 정복하기!이번엔 C#을 공부하면서 헷갈리기 쉬운 개념 중 하나인**값형(Value Type)**과 참조형(Reference Type), 그리고그 사이에서 왔다갔다 하는 박싱(Bo

dev-jen.tistory.com

 

C# - 델리게이트와 람다, 함수도 변수처럼! + LINQ

🎯 델리게이트와 람다 - 함수도 변수처럼 다룰 수 있다?!이번엔 드디어 **델리게이트(delegate)**와 람다(lambda) 개념에 대해 배웠다.처음엔 "함수를 변수처럼 넘긴다고?" 싶어서 어려웠는데,직접 써

dev-jen.tistory.com

 

C# - Nullable 타입과 null 조건 연산자

❓ C# Nullable 타입과 null 조건 연산자 완전 정복프로그래밍을 하다 보면 "값이 없을 수도 있는 상황"이 자주 발생한다.예를 들어, 점수가 없을 수도 있고, 데이터가 존재하지 않을 수도 있다.이럴

dev-jen.tistory.com

 

C# - StringBuilder - 문자열 성능 최적화

🧱 StringBuilder - 문자열 성능 최적화의 열쇠C#에서 문자열을 다룰 때 가장 흔하게 쓰는 건 string 타입이다.그런데 문자열을 반복해서 붙이고 수정할 일이 많아지면,string을 계속 쓰는 건 성능적으

dev-jen.tistory.com

 

1. 콘솔 블랙잭 게임 실습

콘솔 게임을 통해 C#의 조건문, 반복문, 랜덤 숫자 생성, 리스트 사용을 복습했다. 사용자와 딜러가 번갈아 카드를 뽑고 점수를 비교하는 방식으로 진행되며, 게임 흐름 제어에 while, break, continue 등이 효과적으로 사용되었다.


2. 인터페이스 (interface)

interface는 클래스들이 공통적으로 가져야 할 기능(메서드 시그니처)을 정의하는 틀이다. 클래스에 : IAttackable 같은 식으로 상속하며, 명세된 메서드를 반드시 구현해야 한다. 인터페이스는 다중 상속이 가능하다는 점에서 클래스보다 더 유연한 설계가 가능하다.


3. 열거형 (enum)

열거형은 관련된 상수들을 그룹화해 코드를 더 명확하게 만든다.

예:

enum Scene { Start, PlayerInfo, Shop, Inventory, Battle, Dungeon }

숫자 대신 의미 있는 이름으로 분기를 할 수 있어 가독성이 향상된다.


4. 예외 처리 (try-catch)

사용자의 입력이나 연산 과정에서 오류가 발생할 수 있는데, 이를 미리 감지하고 처리할 수 있도록 try-catch 블록을 사용했다. 특히 int.Parse() 실패에 대비해 TryParse()로 안정성을 확보함.


5. 값 형식과 참조 형식, 박싱과 언박싱

int, float 같은 값형은 스택에 저장되고, class나 object는 힙에 저장되는 참조형이다. 값형을 object에 담을 때는 박싱(Boxing), 다시 꺼낼 땐 언박싱(Unboxing)이 일어난다. 메모리 구조를 이해하면 퍼포먼스 최적화에도 도움이 된다.


6. 델리게이트(delegate)와 람다(lambda)

델리게이트는 메서드를 변수처럼 다룰 수 있는 기능이고, 람다는 익명 메서드를 간결하게 작성할 수 있도록 도와주는 문법이다.

Action<string> print = (msg) => Console.WriteLine(msg);

7. Nullable 타입 & null 조건 연산자

값형에 null을 허용하려면 int?, bool? 같은 Nullable 타입을 사용한다. 객체가 null일 수 있는 경우에는 ?. 연산자를 통해 안전하게 접근 가능하다.

player?.ShowStatus();

8. StringBuilder

문자열을 반복적으로 더할 때 + 연산을 반복하는 대신 StringBuilder를 사용하면 성능이 좋아진다. Append(), ToString() 메서드를 사용하여 메모리 할당을 줄인다


9. 삼항 연산자 (? :)

if-else를 간단하게 표현할 수 있는 연산자로,

Console.ForegroundColor = item.Price != 0 ? ConsoleColor.Green : ConsoleColor.Gray;

이렇게 짧고 명확하게 조건 분기를 표현할 수 있다.


10. 텍스트 RPG 시스템 개발

오후에는 Player, Item, Inventory, Shop, GameSystem, Program 클래스를 나누어 텍스트 기반 RPG를 본격적으로 구현했다.

  • 색상을 이용해 UI를 꾸몄고
  • 플레이어 정보, 상점, 인벤토리 메뉴 분기
  • 아이템 구매/판매/장착 기능을 구현
  • enum, List<T>, switch, 삼항 연산자 등 다양한 문법을 실제 적용하며 객체 지향적 구조에 익숙해질 수 있었다.

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

1. Console.WriteLine 출력 시 줄 맞춤이 어긋남

  • 문제정의: 아이템 리스트를 표 형태로 출력할 때 글자들이 좌우로 밀리면서 줄 정렬이 맞지 않았다.
  • 시도: PadRight()와 PadLeft()를 사용한 문자열 너비 조정 함수 AdjustWidth를 만들어서 글자 폭을 고정했다.
  • 해결 방법: 숫자와 문자열의 길이를 고정폭으로 맞추고, 글자의 길이 차이로 생기는 문제를 보완함.
  • 새롭게 알게 된 점: Console 출력은 글자 수 기준이기 때문에 한글과 영어, 이모지가 혼합되면 너비가 다를 수 있음.
  • 다시 만나게 된다면: StringInfo, EastAsianWidth 또는 콘솔 전용 UI 라이브러리 사용 고려.

2. item.Price = 0 대입 오류 (읽기 전용)

  • 문제정의: 아이템 가격을 item.Price = 0으로 수정하려고 하자 읽기 전용 속성이라는 에러 발생.
  • 시도: Item 클래스의 Price 프로퍼티를 확인함.
  • 해결 방법: Price { get; set; }로 set 접근자를 명시적으로 추가하여 수정 가능하게 만듦.
  • 새롭게 알게 된 점: get; private set;처럼 set을 private으로 만들면 외부 클래스에서 값을 변경할 수 없음.
  • 다시 만나게 된다면: 캡슐화가 필요한 상황인지 먼저 판단하고 접근 제한자를 적절히 설정할 것.

3. Scene enum을 switch문에서 사용할 때 case (int)Scene.Start처럼 형변환

  • 문제정의: int를 입력받아 enum과 비교할 때마다 (Scene)Sel로 형변환을 해야 하는 게 번거로움.
  • 시도: enum 이름을 그대로 사용하려고 했지만 컴파일 오류 발생.
  • 해결 방법: Scene scene = (Scene)Sel;로 한 번만 변환한 뒤 switch (scene) 구문 사용.
  • 새롭게 알게 된 점: enum은 int 기반이지만 명시적 형변환이 필요함.
  • 다시 만나게 된다면: Enum.TryParse()를 사용하는 것도 좋은 방법.

4. 삼항 연산자 내 Console.ForegroundColor = ... 문법 오류

  • 문제정의: 다음 코드에서 삼항 연산자 사용 시 문법 오류 발생
  • item.Price != 0 ? Console.ForegroundColor = ConsoleColor.Green : Console.ForegroundColor = ConsoleColor.Gray;
  • 시도: 삼항 연산자 내에 대입문을 넣어 표현하려고 시도
  • 해결 방법: 삼항 연산자는 값의 반환에 적합하며 대입에는 적절하지 않음 → 아래처럼 변경
  • Console.ForegroundColor = item.Price != 0 ? ConsoleColor.Green : ConsoleColor.Gray;
  • 새롭게 알게 된 점: 삼항 연산자는 '값'을 선택하는 표현식이지, 문장(statement)을 수행하는 용도는 아님.
  • 다시 만나게 된다면: 삼항 연산자는 반드시 반환 값이 있는 상황에서만 사용하는 것으로 기억할 것.

📝 메모

아직 구현안된게 너무 많다 하나씩 내일 다시 천천히 만들어보자.. 솔직히 하다보니까 생각보다 머리가 너무 아팠지만 근데 너무 재밌었다!! 내일 열심히 다시 만들어보자!!

 

오늘 하루 정말 많이 배우고, 많이 만들었다!

처음에는 단순한 텍스트 RPG라고 생각했는데, 만들면 만들수록 고려할 게 많고 UI도 신경 써야 할 게 많다는 걸 느꼈다.

C# 콘솔에서도 이렇게 다양한 표현이 가능하다는 게 신기했고, 내가 원하는 디자인을 직접 코딩해서 구현해낼 수 있다는 점이 정말 뿌듯했다.

처음 삼항 연산자 쓸 때 문법 오류 나서 조금 당황했지만, 그 덕분에 삼항 연산자의 역할은 '값 선택'에만 국한된다는 사실을 확실히 배웠다.

또, 콘솔 UI를 깔끔하게 꾸미는 데 문자열 너비 조정, 색상 변경, 이모지, 문자 폭 계산까지 다양한 요소가 필요하다는 걸 체감했다.

특히 오늘은 예외 처리, 박싱/언박싱, Nullable 타입, 람다 등 C#의 문법을 실전 프로젝트에 적용해보면서 훨씬 더 이해가 잘 됐다.

공부는 결국 직접 부딪히면서 구현해봐야 진짜 내 것이 된다는 걸 다시 느낀 하루였다.

내일도 조금씩 꾸준히 발전해나가자!

진짜 RPG 게임이 되는 그날까지! ✨


반응형
반응형

🧱 StringBuilder - 문자열 성능 최적화의 열쇠

C#에서 문자열을 다룰 때 가장 흔하게 쓰는 건 string 타입이다.
그런데 문자열을 반복해서 붙이고 수정할 일이 많아지면,
string을 계속 쓰는 건 성능적으로 비효율적이다!

이때 등장하는 게 바로 StringBuilder다.


✅ string과 StringBuilder의 차이점

구분 string StringBuilder
불변(Immutable) ✅ O ❌ X
변경 시 새로운 문자열 생성 내부 버퍼 수정
성능 느림 (많이 붙이면 메모리 낭비) 빠름 (가변 구조)
용도 문자열 1~2회 조작 반복적 조작 (루프, 누적 등)
 

✅ StringBuilder 기본 사용법

using System.Text;

StringBuilder sb = new StringBuilder();

sb.Append("안녕하세요");
sb.Append(" ");
sb.Append("재은님");

string result = sb.ToString();
Console.WriteLine(result);  // 안녕하세요 재은님
  • Append() : 문자열을 끝에 추가
  • ToString() : 최종 문자열 반환

✅ 자주 사용하는 메서드

메서드 설명
Append(string) 문자열 끝에 추가
AppendLine(string) 문자열 끝에 줄바꿈 포함하여 추가
Insert(index, string) 특정 위치에 문자열 삽입
Remove(index, length) 특정 위치부터 문자열 삭제
Replace(old, new) 특정 문자열을 새 문자열로 치환
Clear() 내용 초기화
Length 현재 길이 반환
 

✅ 실전 예제: 1~5 숫자 연결하기

StringBuilder sb = new StringBuilder();

for (int i = 1; i <= 5; i++)
{
    sb.Append(i);
    sb.Append(", ");
}

sb.Length -= 2; // 마지막 쉼표 제거
Console.WriteLine(sb.ToString()); // 1, 2, 3, 4, 5

이걸 string으로 구현하면, 매 반복마다 새 문자열이 생성되므로 성능 저하 발생!
StringBuilder는 하나의 객체로 내부 버퍼만 수정해서 훨씬 효율적이다.


✅ 언제 쓰면 좋을까?

  • 반복문 안에서 문자열 누적할 때
  • 긴 텍스트를 누적/편집할 때 (예: HTML 생성기, 로그 출력 등)
  • 대량의 문자열 조작이 필요한 상황에서

✅ 정리하며

StringBuilder는 단순히 문자열 붙이기 도구를 넘어서,
C#의 성능을 지키는 필수 도구라고 느꼈다!

처음엔 Append만 알면 되는 것처럼 보이지만,
Insert, Remove, Replace도 알아두면
문자열 처리에서 정말 유용하게 써먹을 수 있다!

처음 알게된 내용인데 유용하게 잘 쓸 수 있겠다!

반응형
반응형

❓ C# Nullable 타입과 null 조건 연산자 완전 정복

프로그래밍을 하다 보면 "값이 없을 수도 있는 상황"이 자주 발생한다.
예를 들어, 점수가 없을 수도 있고, 데이터가 존재하지 않을 수도 있다.
이럴 때 무작정 값을 쓰려고 하면 오류가 발생할 수 있는데,
이걸 안전하게 처리하는 방법이 바로 Nullable 형식null 조건 연산자다!


✅ Nullable 형식 (?)

값 타입에도 null을 허용하도록 해주는 문법

int? score = null;   // OK!
int score = null;    // 오류! 기본 int는 null 불가
  • int, float, bool 같은 값 타입은 기본적으로 null을 가질 수 없음
  • int?, bool?처럼 ?를 붙이면 null을 담을 수 있는 형식이 된다

🔎 사용 예시

int? age = 25;
if (age.HasValue)
    Console.WriteLine(age.Value); // 25 출력
else
    Console.WriteLine("나이 정보 없음");
  • HasValue: 값이 있는지 여부
  • Value: 실제 값을 꺼냄 (null일 경우 예외 발생 주의)

✅ null 조건 연산자 (?.)

객체가 null인지 먼저 확인하고, null이 아니면 멤버에 접근

Player player = null;
int? level = player?.Level;
  • ?. 연산자를 사용하면 player가 null일 경우 null 반환
  • null이 아니면 .Level 값 반환

📌 예시

string name = null;
Console.WriteLine(name?.ToUpper()); // 출력 안됨, null

name = "철수";
Console.WriteLine(name?.ToUpper()); // CHULSU

null이 아니라면 .ToUpper() 실행,
null이라면 예외 없이 null 반환 (에러 없음)


✅ null 병합 연산자 (??)

왼쪽 값이 null이면 오른쪽 기본값 사용

string nickname = null;
string displayName = nickname ?? "이름 없음";

Console.WriteLine(displayName); // 이름 없음
  • ??는 “null이면 이걸 대신 써라”는 뜻
  • ??= 도 가능! (C# 8.0 이상)
nickname ??= "게스트";

✅ 종합 예제

int? score = null;

// 1. null 체크
if (score.HasValue)
    Console.WriteLine(score.Value);
else
    Console.WriteLine("점수 없음");

// 2. null 병합
int finalScore = score ?? 0;
Console.WriteLine($"최종 점수: {finalScore}");

// 3. null 조건 연산자
Player player = null;
int? level = player?.Level;

✅ 언제 쓰면 좋을까?

  • DB에서 받아온 값이 null일 수 있는 경우
  • 게임 캐릭터가 아직 생성되지 않은 경우
  • UI 요소가 아직 초기화되지 않았을 때
  • 선택적 설정값에 기본값 지정하고 싶을 때

✅ 정리하며

처음에는 int?, ?., ?? 같은 문법이 낯설었지만 이해하고 나니까,
null에 대한 코드가 훨씬 깔끔해지는 느낌이었다!

사실 처음 알게된 내용인데 아직 익숙하진 않지만 생각하며 자주 사용해봐야겠다!

✔️ null 체크를 if문으로 일일이 하지 않아도 되고
✔️ 안정적인 코드 작성이 가능하다는 점에서 정말 중요한 기능이다.

반응형
반응형

🎯 델리게이트와 람다 - 함수도 변수처럼 다룰 수 있다?!

이번엔 드디어 **델리게이트(delegate)**와 람다(lambda) 개념에 대해 배웠다.
처음엔 "함수를 변수처럼 넘긴다고?" 싶어서 어려웠는데,
직접 써보니까 유연한 코드 설계를 가능하게 해주는 정말 강력한 기능이라는 걸 느꼈다.


✅ 델리게이트(Delegate)란?

메서드를 참조할 수 있는 변수

함수를 변수처럼 저장하거나, 메서드를 다른 메서드에 전달하고 싶을 때 사용된다.

delegate int Calculate(int x, int y); // 델리게이트 선언

int Add(int a, int b) => a + b;

Calculate calc = Add; // 메서드 참조
Console.WriteLine(calc(3, 5)); // 8 출력
  • delegate 키워드로 델리게이트 타입을 선언
  • 그 델리게이트에 형식이 일치하는 메서드를 할당 가능
  • 마치 메서드를 변수처럼 다룰 수 있다!

🧠 델리게이트는 왜 필요할까?

  • 콜백 구조를 만들 수 있다 (ex. 버튼 클릭 시 실행할 메서드 등록)
  • 이벤트 처리 구조를 만들 수 있다 (ex. 공격했을 때 데미지 전달)
  • 재사용성과 유연성을 높일 수 있다 (실제 호출 메서드는 나중에 정함)

🔁 여러 메서드 등록도 가능하다!

delegate void LogHandler(string msg);

void LogToConsole(string msg) => Console.WriteLine(msg);
void LogToFile(string msg) => File.WriteAllText("log.txt", msg);

LogHandler logger = LogToConsole;
logger += LogToFile;

logger("프로그램 시작!"); // 두 메서드 모두 실행됨

+= 으로 메서드를 연결(멀티캐스트) 할 수 있고,
-= 으로 제거도 가능하다.


✅ 람다식(Lambda)이란?

이름 없는 메서드를 간단하게 표현하는 방식

Calculate calc = (x, y) => x + y;
Console.WriteLine(calc(4, 6)); // 10 출력
  • delegate 키워드 없이도 메서드처럼 동작
  • 간단한 로직은 코드 길이를 엄청 줄여줌!

📌 람다 문법

 
(매개변수) => 표현식 또는 { 실행 블록 }
Action<string> printer = msg => Console.WriteLine("출력: " + msg);
printer("안녕하세요!");
  • 매개변수가 하나면 괄호 생략 가능
  • 리턴이 필요 없으면 Action, 리턴이 있으면 Func로 처리

✅ 실전 예제: 이벤트에 콜백 등록하기

public delegate void AttackHandler(int damage);

public class Enemy
{
    public event AttackHandler OnAttack;

    public void Attack()
    {
        OnAttack?.Invoke(10); // 콜백 호출
    }
}

public class Player
{
    public void TakeDamage(int damage)
    {
        Console.WriteLine($"플레이어가 {damage} 데미지를 입었습니다.");
    }
}

// 사용
Enemy enemy = new Enemy();
Player player = new Player();

enemy.OnAttack += player.TakeDamage;
enemy.Attack(); // → 데미지 전달됨
  • event 키워드를 사용하면 델리게이트를 외부에서 직접 호출하는 걸 방지할 수 있다.
  • OnAttack += 함수 식으로 이벤트에 메서드를 등록하고,
  • Invoke()로 콜백을 실행한다.

🔍 LINQ 완전 정복 - 컬렉션을 자유자재로 다루자!

이번에 공부한 LINQ는 정말 마법 같았다 ✨
배열이나 리스트에서 데이터를 꺼낼 때마다 반복문을 돌리는 게 너무 귀찮았는데,
LINQ를 사용하니까 마치 SQL처럼 직관적으로 데이터를 조회할 수 있었다!


✅ LINQ란?

**컬렉션 데이터(Queryable 데이터)**를 간결하게 다루는 문법

  • 배열, 리스트, 딕셔너리 등에서 원하는 데이터를 쉽게 뽑아올 수 있음
  • where, select, order by 같은 SQL과 비슷한 구문으로 필터링
  • 내부적으로는 반복문이지만, 코드를 훨씬 읽기 좋게 만듦

✅ LINQ 기본 문법 2가지

📌 1. 쿼리 구문 (SQL 스타일)

int[] numbers = { 1, 2, 3, 4, 5 };

var even = from n in numbers
           where n % 2 == 0
           select n;

foreach (var num in even)
    Console.WriteLine(num); // 2, 4
  • from ~ in ~ 으로 컬렉션 순회
  • where는 조건 필터링
  • select는 어떤 값을 뽑을지 지정

📌 2. 메서드 구문 (람다식 기반)

var even = numbers.Where(n => n % 2 == 0);

foreach (var num in even)
    Console.WriteLine(num); // 2, 4
  • Where는 조건 필터
  • Select는 변형
  • 람다식과 찰떡궁합!

✅ LINQ 자주 쓰는 메서드 정리

메서드 설명
Where() 조건에 맞는 요소 필터링
Select() 원하는 형태로 변형
OrderBy() 오름차순 정렬
OrderByDescending() 내림차순 정렬
First() 첫 번째 요소 반환 (없으면 예외)
FirstOrDefault() 첫 번째 요소 or 기본값 반환
Any() 조건에 맞는 요소가 있는지 확인 (bool)
All() 모든 요소가 조건을 만족하는지 확인
Count() 요소 개수 반환
ToList() 결과를 리스트로 변환
Distinct() 중복 제거
GroupBy() 특정 키 기준으로 그룹화
 

✅ 예제: 학생 성적에서 필터링해보기

class Student
{
    public string Name { get; set; }
    public int Score { get; set; }
}

List<Student> students = new List<Student>
{
    new Student { Name = "철수", Score = 85 },
    new Student { Name = "영희", Score = 92 },
    new Student { Name = "민수", Score = 78 },
};

// 점수 80점 이상 학생 필터링
var top = students.Where(s => s.Score >= 80)
                  .OrderByDescending(s => s.Score);

foreach (var s in top)
    Console.WriteLine($"{s.Name} - {s.Score}점");

람다식으로 Where, OrderBy도 연결해서 사용 가능!


✅ LINQ는 어디에 쓰일까?

  • 게임에서 조건에 맞는 오브젝트 찾기
  • UI 리스트에서 필터/정렬 기능 구현
  • 서버에서 받아온 데이터 가공
  • 배열/딕셔너리 탐색/변환 등

✅ 정리하며

델리게이트는 "함수를 변수처럼 다룰 수 있다"는 점에서 진짜 좋은 기능이었다.
처음에는 어렵게 느껴졌지만, 이벤트 처리 구조나 콜백 방식에 자주 쓰이고,
나중엔 Unity 에서도 굉장히 자주 만나게 될 것 같다.

람다식은 짧고 간결하게 코드를 표현할 수 있게 해주고,
Action, Func 같은 델리게이트 타입과 함께 쓰면 정말 좋은듯 하다!

 

LINQ는 처음엔 생소하지만, 쓰면 쓸수록
더 적은 코드로 더 많은 일을 할 수 있게 해주는 도구다!

특히 람다식과 함께 쓰면 엄청 좋을듯 하다.

 

사실 저번에 델리게이트와 람다식, LINQ를 다뤄봤었지만 이번에 강의를 들으면서 다시 정리해봤다!

 

C# - Delegate(델리게이트) + Lambda operator(람다 연산자) + Lambda(람다식)

✅ 델리게이트(Delegate)란?📌 델리게이트는 메서드를 참조할 수 있는 타입이다.쉽게 말하면, **"함수를 변수처럼 다루기 위한 문법"**이야.C#에서 함수 자체는 변수에 담을 수 없지만,델리게이트를

dev-jen.tistory.com

저번에 정리한 델리게이트와 람다식이다!

 

C# - Linq(Language Integrated Query)

🧠 LINQ란?LINQ는 "Language Integrated Query"의 약자로,C# 코드 안에서 SQL처럼 데이터를 쉽게 조회하고 가공할 수 있게 해주는 문법이야.✅ LINQ의 목적배열, 리스트, 딕셔너리, 데이터베이스 등 여러 종류

dev-jen.tistory.com

이건 Linq!

반응형
반응형

📦 값형과 참조형, 그리고 박싱과 언박싱 완전 정복하기!

이번엔 C#을 공부하면서 헷갈리기 쉬운 개념 중 하나인
**값형(Value Type)**과 참조형(Reference Type), 그리고
그 사이에서 왔다갔다 하는 박싱(Boxing)과 언박싱(Unboxing) 개념을 정리해봤다.

처음에는 그냥 값이냐 참조냐 하고 넘겼는데,
실제로 메모리에 어떻게 저장되고 전달되는지를 이해하니까
왜 중요한지 확실히 느껴졌다!


✅ 값형(Value Type)이란?

값을 "그 자체"로 저장하는 타입

  • 변수에 값이 직접 저장
  • 다른 변수에 복사할 때 값이 그대로 복사
  • 대표적인 예: int, float, bool, char, struct 등
 
int a = 10;
int b = a;
b = 20;
Console.WriteLine(a); // 10

a와 b는 완전히 다른 공간에 저장된 값을 가진다. 하나 바꿨다고 다른 게 바뀌지 않음!


✅ 참조형(Reference Type)이란?

값을 "어디 있는지(주소)"로 저장하는 타입

  • 변수에 **값이 있는 위치(참조)**가 저장됨
  • 다른 변수에 복사하면 같은 객체를 참조하게 됨
  • 대표적인 예: class, array, string, object, interface 등
int[] arr1 = new int[] { 1, 2, 3 };
int[] arr2 = arr1;
arr2[0] = 99;
Console.WriteLine(arr1[0]); // 99

arr1과 arr2는 같은 배열을 참조하고 있어서, 하나를 바꾸면 둘 다 바뀐다!


✅ 값형과 참조형의 차이

구분 값형(Value Type) 참조형(Reference Type)
저장 방식 값을 직접 저장 주소(참조)를 저장
복사 시 값이 복사됨 참조가 복사됨
저장 위치 스택(Stack) 힙(Heap) + 참조는 스택
대표 타입 int, float, bool, struct class, array, string
 

🧳 박싱(Boxing)과 언박싱(Unboxing)이란?

🟩 박싱(Boxing)

값형 → 참조형으로 변환

int num = 123;
object obj = num;  // 박싱
  • num은 스택에 있던 값형인데,
  • object로 저장되면서 힙에 복사되고,
  • 참조형처럼 다뤄지게 된다.

🟥 언박싱(Unboxing)

참조형 → 값형으로 변환

object obj = 123;        // 박싱
int num = (int)obj;      // 언박싱
  • 다시 값을 꺼낼 때는 명시적 형변환이 필요하다.
  • 형이 잘못되면 런타임 오류 발생하니 주의해야 한다!

🔍 예제: 값형 vs 참조형 + 박싱/언박싱

int x = 10;
object boxed = x; // 박싱
int y = (int)boxed; // 언박싱

Console.WriteLine($"x: {x}");        // 10
Console.WriteLine($"boxed: {boxed}"); // 10
Console.WriteLine($"y: {y}");        // 10
  • 박싱된 boxed는 힙에 저장된 object 타입 객체
  • 언박싱된 y는 다시 스택에 저장된 값형 타입

⚠️ 박싱/언박싱 주의할 점

  • 성능 저하 발생 가능 (힙 할당 + 가비지 컬렉션 대상)
  • 형변환 필수, 실수하면 InvalidCastException
  • 자주 쓰는 곳: ArrayList, List<object>, 비박형 컬렉션 등

📝 마무리하며

이번 개념은 정말 눈에 안 보이지만 중요한 개념이라는 걸 깨달았다.

단순히 변수에 뭔가를 넣고 꺼내는 게 아니라,
그 값이 메모리에 어디에 있고,
복사됐는지, 공유되는지, 비용이 얼마나 드는지까지
다 고려해야 더 효율적이고 안전한 코드를 작성할 수 있다는 걸 배웠다!

반응형
반응형

✅ 예외 처리(Exception Handling) - 프로그램을 안전하게 지키는 방패

C#을 비롯한 대부분의 언어에서는 **예외(Exception)**라는 개념이 있다.
예외는 프로그램 실행 중에 발생하는 예상하지 못한 상황을 의미하고,
이걸 제대로 처리하지 않으면 프로그램이 갑자기 종료될 수 있다.

그렇기 때문에 예외는 단순한 에러가 아니라,
예외 상황에 대응하는 코드를 반드시 작성해줘야 한다.


📌 예외란 무엇인가요?

예외는 다음과 같은 상황에서 발생할 수 있다:

  • 0으로 나누기
  • 존재하지 않는 파일 열기
  • 배열 인덱스 초과 접근
  • null 객체 사용 등등

이런 예외가 발생하면 C#에서는 프로그램을 중단하고 예외 메시지를 출력한다.
하지만 우리가 try-catch 문을 사용하면 예외를 잡아서 직접 처리할 수 있다.


🔧 기본 구조: try-catch-finally

try
{
    // 예외가 발생할 수 있는 코드
}
catch (Exception ex)
{
    // 예외 발생 시 실행할 코드
}
finally
{
    // 예외 발생 여부와 관계없이 무조건 실행됨
}

🎯 예제: 0으로 나누기

try
{
    int result = 10 / 0;
    Console.WriteLine("결과: " + result);
}
catch (DivideByZeroException ex)
{
    Console.WriteLine("0으로 나눌 수 없습니다.");
}
finally
{
    Console.WriteLine("finally 블록이 실행됩니다.");
}
  • try: 문제가 생길 수 있는 코드 작성
  • catch: 문제가 생기면 이곳에서 처리
  • finally: 문제가 생겼든 안 생겼든 무조건 실행됨

🧱 여러 개의 catch 사용하기

try
{
    // 여러 예외가 발생할 수 있는 코드
}
catch (FormatException)
{
    Console.WriteLine("입력 형식이 잘못되었습니다.");
}
catch (NullReferenceException)
{
    Console.WriteLine("객체가 null입니다.");
}
catch (Exception ex)
{
    Console.WriteLine("예외 발생: " + ex.Message);
}
  • catch는 위에서부터 차례로 실행되며, 가장 먼저 매칭되는 예외 블록이 실행된다.
  • 보다 구체적인 예외 타입부터 먼저 작성하는 게 중요하다!

🧑‍💻 사용자 정의 예외

예외는 기본 제공만 쓰는 게 아니라 직접 정의해서 만들 수도 있다.

public class NegativeNumberException : Exception
{
    public NegativeNumberException(string message) : base(message) { }
}

try
{
    int num = -5;
    if (num < 0)
        throw new NegativeNumberException("음수는 허용되지 않습니다.");
}
catch (NegativeNumberException ex)
{
    Console.WriteLine(ex.Message);
}
  • throw 키워드를 사용하면 의도적으로 예외를 발생시킬 수 있다.
  • Exception 클래스를 상속받아 새로운 예외를 정의할 수 있다.

🔄 finally 블록 언제 쓰나요?

  • 파일, 네트워크, DB 연결 등은 예외 발생 여부와 상관없이 정리 작업이 필요하다.
  • 이럴 때 finally를 사용하면 무조건 실행되므로 리소스 해제에 좋다!
FileStream file = null;
try
{
    file = File.Open("data.txt", FileMode.Open);
}
catch (IOException ex)
{
    Console.WriteLine("파일 열기에 실패했습니다.");
}
finally
{
    if (file != null)
        file.Close(); // 무조건 닫힘!
}

🚧 예외 처리 시 주의사항

주의할 점 설명
너무 포괄적인 catch 사용 ❌ catch (Exception)만 쓰면 디버깅이 어려워진다
가능한 구체적인 예외 먼저 ✅ DivideByZeroException, FormatException 등 먼저 작성
로직은 try 밖에, 예외만 try 안에 예외가 발생할 수 있는 코드만 try에 넣는 게 좋다
finally는 리소스 해제에 활용 Close, Dispose 같은 정리 작업은 finally 안에서
반응형
반응형

✅ 인터페이스란 무엇인가요?

객체지향 프로그래밍을 공부하면서 인터페이스는 정말 많이 등장하는 개념이다.
클래스와 비슷해 보이지만, 사용하는 목적이 분명히 다르다.
이번 글에서는 인터페이스의 개념부터 왜 필요한지, 어떻게 사용하는지까지
예제와 함께 하나씩 정리해보려고 한다.


📌 다중 상속이 왜 위험한가요?

C#은 클래스 간의 다중 상속을 허용하지 않는다.
그 이유는 대표적으로 아래와 같은 문제가 있기 때문이다.

🧱 1. 다이아몬드 문제(Diamond Problem)

  • A → B, A → C, B와 C → D 를 상속받으면
  • A의 멤버가 D에 중복 상속되어 어떤 걸 써야 하는지 모호해진다.
  • 이런 모호성을 해결하려다 보면 코드가 더러워진다 😵

🔀 2. 복잡한 상속 구조

  • 여러 클래스를 동시에 상속받으면 클래스 간 관계가 꼬인다.
  • 디버깅, 유지보수, 확장이 어려워진다.

🔧 3. 이름 충돌 발생

  • 같은 이름의 멤버가 두 부모 클래스에 있을 때,
  • 어떤 걸 써야 하는지 애매해지고 충돌 처리 코드가 필요하다.

🧘‍♂️ 4. C#의 선택: 단일 상속 + 인터페이스

  • C#은 클래스는 단일 상속만 허용하고, 인터페이스는 다중 구현 가능하게 설계했다.
  • 덕분에 클래스 구조는 깔끔하게 유지하고, 다형성은 인터페이스로 처리할 수 있다!

🎯 그래서 인터페이스가 등장합니다

인터페이스는 쉽게 말하면 **“해야 할 일 목록(To-Do List)”**를 만드는 것과 같다.

public interface IMovable
{
    void Move(int x, int y);
}

이 인터페이스를 어떤 클래스가 "구현한다"고 선언하면, 반드시 안에 정의된 기능을 직접 만들어야 한다!

public class Player : IMovable
{
    public void Move(int x, int y)
    {
        // 플레이어 이동 구현
    }
}

🔄 인터페이스는 다중 구현이 가능하다!

public interface IUsable { void Use(); }
public interface IDroppable { void Drop(); }

public class Item : IUsable, IDroppable
{
    public void Use() { ... }
    public void Drop() { ... }
}

이렇게 클래스 하나에 여러 개의 인터페이스를 동시에 붙일 수 있다.
이건 다중 상속은 아니지만, 다중 상속처럼 여러 기능을 "약속"하고 "구현"하는 구조다!


🆚 인터페이스 vs 추상 클래스

항목 인터페이스 추상클래스
상속 가능 수 여러 개 (다중 구현) 하나만 (단일 상속)
구현 포함 여부 메서드 구현 불가 (.NET 5 이전 기준) 일부 구현 가능
목적 동작 명세 (계약) 공통 기능 제공
결합도 낮음 (느슨한 연결) 높음 (강한 연결)
 

✅ 정리하자면…

  • 인터페이스는 계약이다. "이 기능 반드시 구현해!"라는 강제성이 있다.
  • C#에서는 클래스 간 다중 상속을 피하고, 인터페이스를 통해 다형성을 구현한다.
  • 인터페이스를 사용하면 유지보수가 쉬운 구조, 유연한 설계가 가능해진다.

 

 

✅ 열거형 (enum) - 의미 있는 상수 만들기

프로그래밍을 하다 보면 의미 있는 상수 값들을 자주 다루게 된다.
예를 들어, 요일을 숫자로 0~6으로 다루는 것보다 Monday, Tuesday 같은 이름으로 다루는 게
훨씬 가독성도 좋고 실수도 줄일 수 있다.

이럴 때 등장하는 게 바로 **열거형(enum)**이다!


📌 열거형이란?

  • 열거형(enum)은 관련된 상수들의 집합이다.
  • 각 상수는 내부적으로 정수 값을 갖는다.
  • 열거형을 사용하면 코드가 가독성 있고 의미 있게 바뀐다.
enum DaysOfWeek
{
    Sunday,    // 0
    Monday,    // 1
    Tuesday,   // 2
    Wednesday, // 3
    Thursday,  // 4
    Friday,    // 5
    Saturday   // 6
}

위와 같이 선언하면, Monday는 내부적으로 1이라는 값을 가진다.


🎯 왜 열거형을 사용할까?

  1. 가독성 증가
    → if (day == 1) 보다 if (day == DaysOfWeek.Monday)가 훨씬 명확하다!
  2. 코드 실수 방지
    → 상수 값을 마음대로 입력하는 실수를 막을 수 있다.
  3. 코드 유지보수 편리
    → 의미 있는 이름으로 값들을 표현하면 추후 수정이 쉬워진다.

🛠️ 열거형 기본 사용법

🔹 열거형 선언

enum GameState
{
    MainMenu,
    Playing,
    Paused,
    GameOver
}

🔹 변수 선언과 사용

GameState state = GameState.Playing;

if (state == GameState.Paused)
{
    Console.WriteLine("게임이 일시정지되었습니다.");
}

🔹 열거형 → 정수형 형변환

int value = (int)GameState.Playing; // 결과: 1

🔹 정수형 → 열거형 형변환

GameState state = (GameState)2; // Paused

⚠️ 열거형에서 주의할 점

  • 열거형은 내부적으로 int형 정수값을 갖는다.
    따라서 정수값으로 캐스팅할 수 있지만, 정의되지 않은 값도 캐스팅은 되므로 주의해야 한다.
GameState state = (GameState)100;
Console.WriteLine(state); // 출력은 되지만 실제로는 존재하지 않는 값

✅ switch문과 함께 사용하기

switch (state)
{
    case GameState.MainMenu:
        Console.WriteLine("메인 메뉴입니다.");
        break;
    case GameState.Playing:
        Console.WriteLine("게임을 플레이 중입니다.");
        break;
    case GameState.Paused:
        Console.WriteLine("일시정지 상태입니다.");
        break;
    case GameState.GameOver:
        Console.WriteLine("게임 오버입니다.");
        break;
}

enum은 switch문에서 진짜 자주 쓰인다! 실수 없이 명확한 분기 처리가 가능해져서 정말 유용하다.


🧪 실습 예제: 월(Month) 처리하기

public enum Month
{
    January = 1,
    February,
    March,
    April,
    May,
    June,
    July,
    August,
    September,
    October,
    November,
    December
}

void PrintMonthName(int month)
{
    if (month >= 1 && month <= 12)
    {
        Month m = (Month)month;
        Console.WriteLine($"선택한 월은 {m}입니다.");
    }
    else
    {
        Console.WriteLine("잘못된 월입니다.");
    }
}

✨ 마무리하며

처음엔 그냥 숫자 쓰면 되지 싶었는데,
막상 열거형을 쓰고 나면 가독성, 실수 방지, 유지보수 모든 면에서 장점이 크다.
특히 게임 상태, 방향, 아이템 등 명확한 상태 구분이 필요한 경우에 정말 자주 쓰인다.

이번 정리로 다음 코딩부터는 잘 써보자!

반응형
반응형

1. 게임 기획

  • 게임 목표 정의 (21점을 넘지 않으면서 딜러보다 높은 점수 획득)
  • 플레이어 수 (1인 vs 딜러)
  • 승패 조건 정의
  • 카드 규칙 요약 (A=1 또는 11, J/Q/K=10, 숫자카드=해당 숫자)

2. 기본 구조 설계

  • 클래스 설계
    • Card (문양, 숫자)
    • Deck (카드 덱, 섞기, 카드 뽑기)
    • Player (카드 리스트, 점수 계산)
    • GameManager (게임 흐름 제어)
  • Enum 설계
    • Suit (Hearts, Spades, Clubs, Diamonds)
    • Rank (Ace ~ King)

3. 게임 로직 구현

  • 카드 덱 생성 및 셔플
  • 플레이어/딜러에게 카드 2장씩 배분
  • 점수 계산 로직 (A의 처리 포함)
  • 플레이어 선택 (Hit / Stand)
  • 딜러 동작 (점수 17 이상일 때까지 Hit)
  • 승패 판정 및 출력

4. 입출력 처리

  • 콘솔 출력 (카드 정보, 점수 등)
  • 사용자 입력 처리 (Console.ReadLine())

5. 게임 반복 및 종료 처리

  • 다시 시작 여부 묻기
  • 플레이어, 덱 초기화 로직 추가
using System;
using System.Collections.Generic;

// 카드 무늬 정의
public enum Suit { Hearts, Diamonds, Clubs, Spades }

// 카드 숫자 정의 (J/Q/K는 10, Ace는 11로 처리)
public enum Rank
{
    Two = 2, Three, Four, Five, Six, Seven, Eight, Nine, Ten,
    Jack = 10, Queen = 10, King = 10, Ace = 11
}

// 개별 카드 클래스
public class Card
{
    public Suit Suit { get; }
    public Rank Rank { get; }

    public Card(Suit suit, Rank rank)
    {
        Suit = suit;
        Rank = rank;
    }

    // 카드의 점수 반환
    public int GetValue() => (int)Rank;

    public override string ToString()
    {
        return $"{Rank} of {Suit}";
    }
}

// 카드 덱 클래스
public class Deck
{
    private List<Card> cards = new List<Card>();
    private Random rng = new Random();

    public Deck()
    {
        // 모든 카드 조합 생성
        foreach (Suit suit in Enum.GetValues(typeof(Suit)))
        {
            for (int i = 2; i <= 10; i++)
                cards.Add(new Card(suit, (Rank)i));

            cards.Add(new Card(suit, Rank.Jack));
            cards.Add(new Card(suit, Rank.Queen));
            cards.Add(new Card(suit, Rank.King));
            cards.Add(new Card(suit, Rank.Ace));
        }

        Shuffle();
    }

    // 카드 섞기
    public void Shuffle()
    {
        for (int i = 0; i < cards.Count; i++)
        {
            int j = rng.Next(i, cards.Count);
            (cards[i], cards[j]) = (cards[j], cards[i]);
        }
    }

    // 카드 한 장 뽑기
    public Card DrawCard()
    {
        Card card = cards[0];
        cards.RemoveAt(0);
        return card;
    }
}

// 플레이어 클래스 (플레이어 및 딜러 공통 사용)
public class Player
{
    public string Name { get; }
    public List<Card> Hand { get; private set; } = new List<Card>();

    public Player(string name)
    {
        Name = name;
    }

    public void AddCard(Card card) => Hand.Add(card);
    public void ClearHand() => Hand.Clear();

    // 점수 계산 (Ace = 11 또는 1로 조절)
    public int GetScore()
    {
        int score = 0;
        int aceCount = 0;

        foreach (var card in Hand)
        {
            int value = card.GetValue();
            score += value;
            if (card.Rank == Rank.Ace) aceCount++;
        }

        // 점수가 21 초과면 Ace를 1로 조정
        while (score > 21 && aceCount > 0)
        {
            score -= 10;
            aceCount--;
        }

        return score;
    }

    // 플레이어의 카드 보여주기
    public void ShowHand(bool showAll = true)
    {
        Console.WriteLine($"\n[{Name}의 카드]");
        for (int i = 0; i < Hand.Count; i++)
        {
            if (!showAll && i == 0)
                Console.WriteLine("[첫 번째 카드는 비공개]");
            else
                Console.WriteLine(Hand[i]);
        }

        if (showAll)
            Console.WriteLine($"현재 점수: {GetScore()}");
    }
}

// 게임 전체를 관리하는 클래스
public class GameManager
{
    private Deck deck;
    private Player player;
    private Player dealer;

    // 게임 시작
    public void StartGame()
    {
        deck = new Deck();
        player = new Player("플레이어");
        dealer = new Player("딜러");

        Console.Clear();
        Console.WriteLine("=== 블랙잭 게임을 시작합니다 ===");

        DealInitialCards();
        PlayerTurn();

        if (player.GetScore() <= 21)
            DealerTurn();

        ShowResult();
    }

    // 처음 카드 두 장씩 배분
    private void DealInitialCards()
    {
        player.ClearHand();
        dealer.ClearHand();

        player.AddCard(deck.DrawCard());
        dealer.AddCard(deck.DrawCard());
        player.AddCard(deck.DrawCard());
        dealer.AddCard(deck.DrawCard());

        player.ShowHand();
        dealer.ShowHand(false); // 딜러는 한 장 숨김
    }

    // 플레이어 턴
    private void PlayerTurn()
    {
        while (true)
        {
            Console.WriteLine("\n카드를 더 받으시겠습니까? (H: Hit, S: Stand)");
            string input = Console.ReadLine()?.ToLower();

            if (input == "h")
            {
                player.AddCard(deck.DrawCard());
                player.ShowHand();

                if (player.GetScore() > 21)
                {
                    Console.WriteLine("버스트! 21점을 초과했습니다.");
                    return;
                }
            }
            else if (input == "s")
            {
                return;
            }
            else
            {
                Console.WriteLine("잘못된 입력입니다. H 또는 S를 입력해주세요.");
            }
        }
    }

    // 딜러 턴 (17 이상일 때 멈춤)
    private void DealerTurn()
    {
        Console.WriteLine("\n딜러의 턴 시작");
        dealer.ShowHand();

        while (dealer.GetScore() < 17)
        {
            Console.WriteLine("딜러가 카드를 받습니다...");
            dealer.AddCard(deck.DrawCard());
            dealer.ShowHand();
        }

        if (dealer.GetScore() > 21)
            Console.WriteLine("딜러 버스트! 21점을 초과했습니다.");
        else
            Console.WriteLine("딜러가 멈췄습니다.");
    }

    // 최종 결과 출력
    private void ShowResult()
    {
        int playerScore = player.GetScore();
        int dealerScore = dealer.GetScore();

        Console.WriteLine("\n=== 게임 결과 ===");
        player.ShowHand();
        dealer.ShowHand();

        if (playerScore > 21)
            Console.WriteLine("패배하셨습니다.");
        else if (dealerScore > 21 || playerScore > dealerScore)
            Console.WriteLine("승리하셨습니다!");
        else if (playerScore < dealerScore)
            Console.WriteLine("패배하셨습니다.");
        else
            Console.WriteLine("무승부입니다.");
    }
}

// 프로그램 진입점
class Program
{
    static void Main(string[] args)
    {
        while (true)
        {
            GameManager game = new GameManager();
            game.StartGame();

            Console.WriteLine("\n다시 시작하시겠습니까? (Y/N)");
            string input = Console.ReadLine()?.ToLower();
            if (input != "y")
                break;
        }

        Console.WriteLine("게임을 종료합니다.");
    }
}

 

와,,너무 어렵다.. 하나씩 모르는거 다시 정리해야겠다...

 

✅ 1. 자동 구현 프로퍼티는 뭘까?

public Suit Suit { get; }
public Rank Rank { get; }

처음에는 { get; }만 써 있는 게 뭔지 잘 몰랐다. 근데 이건 자동 구현 프로퍼티라고 해서
별도로 필드를 선언하지 않아도, C#이 자동으로 내부에 저장공간을 만들어주는 기능이었다!

즉, 아래 코드처럼

private Suit _suit;
public Suit Suit { get { return _suit; } }

간단하게 한 줄로 줄여서 표현한 게 바로:

public Suit Suit { get; }

이런 식으로 get만 있으면 외부에서 읽기만 가능하고, 값을 바꾸는 건 불가능하다는 뜻이다.
초기화는 생성자에서만 가능하다.


✅ 2. Rank는 enum인데 Ace가 들어오면 어떻게 11이 되는 걸까?

public enum Rank
{
    Two = 2, Three, ..., Jack = 10, Queen = 10, King = 10, Ace = 11
}

처음엔 enum이라는 게 단순히 이름 붙은 그룹이라고 생각했는데,
이렇게 = 숫자를 붙여주면 실제로 숫자 값을 가질 수 있다는 걸 알게 됐다!

그리고 아래 코드를 보면:

public int GetValue() => (int)Rank;

여기서 Rank는 enum인데, (int)Rank라고 형변환을 해주면
정수 값으로 바뀌어서 리턴된다. 그래서 Rank.Ace가 들어오면 11이 되는 거다!
오... 이런 식으로 enum은 이름을 갖고 있지만 숫자로도 바꿀 수 있는건 몰랐다!


✅ 3. foreach (Suit suit in Enum.GetValues(typeof(Suit))) 이건 어려웠다...

처음엔 이 한 줄이 뭔 말인지 하나도 안 보였다.
근데 하나씩 뜯어보니까 이렇게 해석할 수 있었다:

  • Enum.GetValues(typeof(Suit))
    → Suit 열거형에 정의된 값들을 전부 가져온다. 결과는 배열로 나온다.
    → 즉 [Hearts, Diamonds, Clubs, Spades] 이런 배열이 생김
  • foreach (Suit suit in ...)
    → 그 배열을 하나씩 꺼내서 suit 변수에 담아 반복한다는 뜻

결국 이 코드는 모든 무늬(Suit)를 하나씩 꺼내서 루프를 돌리는 것이다!

foreach (Suit suit in Enum.GetValues(typeof(Suit)))
{
    // suit는 Hearts → Diamonds → Clubs → Spades 순으로 반복
}

와... 처음엔 진짜 괴랄했는데 알고 나니까 엄청 유용한 문법이었다 😅


✅ 4. (cards[i], cards[j]) = (cards[j], cards[i]); 이건 뭐지?

처음에는 그냥 한 줄 스왑처럼 보였는데, 진짜로 되는 건가 싶었다.
찾아보니까 이건 튜플 스왑 문법이었고, C# 7.0 이상부터 지원되는 기능이었다!

즉, 아래 코드랑 똑같은 기능이다:

var temp = cards[i];
cards[i] = cards[j];
cards[j] = temp;

하지만 위 코드는 3줄이고, 아래는 단 1줄:

(cards[i], cards[j]) = (cards[j], cards[i]);

이게 된다니... 정말 깔끔하고 예쁘다!
근데 처음 봤을 땐 이게 뭐야? 싶을 정도로 생소했던 문법이었다!

사실 난 temp로 그냥 쓰는게 더 익숙해서..잘 기억해놨다가 써먹어야겠다!


✅ 5. 그 외에도 새롭게 이해한 문법들

Card card = cards[0];
cards.RemoveAt(0);
return card;

→ 이건 덱에서 맨 앞에 있는 카드 한 장을 꺼내고, 덱에서 제거해서 리턴하는 부분이다.

public List<Card> Hand { get; private set; } = new List<Card>();

→ 플레이어의 카드 리스트. 외부에서는 읽기만 가능하고 수정은 불가능.

foreach (var card in Hand)
{
    int value = card.GetValue();
    score += value;
    if (card.Rank == Rank.Ace) aceCount++;
}

→ 카드 하나씩 꺼내서 점수 누적하고, Ace가 몇 개인지도 따로 세서
21 초과 시 점수 조정할 수 있도록 준비하는 로직이다.

 

이번 블랙잭 게임을 만들면서 솔직히 다 만들지는 못했지만 모르는 문법도 꽤 있었다.

너무 어려웠지만 하나씩 차근차근 하면서 다시 또 배우면 된다!

항상 겸손하게 하나하나씩 천천히 배워가자! 개발자는 겸손할 수 밖에 없는 직업이기 때문이다.. 모르는게 너무많아..ㅠㅜ

반응형