(수)2024.09.25 자 끄적끄적...
00시부터 밤 새서 적은 내용이 포함되어 있다. 원래 자기전꺼를 다 넣으려고 했는데 이전꺼에 넣긴 좀 많아서...
근데 아침에 8시 넘어서 잠든 건 안비밀. 그냥 오늘 쓴 내용이다.
뭔가 중요한 게 좀 많다.
- 다이아가 싫은 다중 상속 ㅠㅠ<이거 없애주세요
뭐 계속 똑같은 얘기 하네ㅡㅡ< 이것도 없애주세요
public interface IMovable
{
void Move(int x, int y); // 이동 메서드 선언
}
public class Player : IMovable
{
public void Move(int x, int y)
{
// 플레이어의 이동 구현
}
}
public class Enemy : IMovable
{
public void Move(int x, int y)
{
// 적의 이동 구현
}
}
IMovable movableObject1 = new Player();
IMovable movableObject2 = new Enemy();
movableObject1.Move(5, 0); // 플레이어 이동
movableObject2.Move(1, 9); // 적 이동
솔직히 말해서 처음엔 추상클래스와의 차이점을 느끼지 못하였다.
그런데 생각해보니 추상클래스는 클래스라 다중상속이 안되니까 정해져 있는 클래스에 계속 넣어놔야 되고 유동적이지 못하다는 생각이 팍 들었다.
만약 추상클래스를 이용해 여려 군대에 똑같은 인터페이스를 만드려고 한다면 추상클래스(나 메서드)가 속한 클래스의 전체를 상속하여야 할 것이다.
근데 그래도 아직 애매한 부분이 있다. 어차피 선언만 하고 들어가서 다시 만들어줘야 하는데 굳이 이렇게 형식을 만들어놓을 필요가 있을까...? 라는?
그런데 다음 예제를 공부하면서 이걸 사용해야만 하는 이유에 대해 알아버렸다.
// 아이템을 사용할 수 있는 인터페이스
public interface IUsable
{
void Use();
}
// 아이템 클래스
public class Item : IUsable
{
public string Name { get; set; }
public void Use()
{
Console.WriteLine("아이템 {0}을 사용했습니다.", Name);
}
}
// 플레이어 클래스
public class Player
{
public void UseItem(IUsable item)
{
item.Use();
}
}
// 게임 실행
static void Main()
{
Player player = new Player();
Item item = new Item { Name = "Health Potion" };
player.UseItem(item);
}
이 코드를 처음 보고 든 생각은, 아니, Player클래스의 UseItem의 매개변수로 왜 IUsable 타입을 받는 거지? Item 타입을 받아서 쓰는 게 더 가독성도 좋고 IUseble엔 Use()의 구현부가 선언도 안 되어 있는데 저게 실행이 되긴 하는건가?
라는 거였다.
그런데, 천만의 말씀 만만의 콩떡. 그게 아니다. 이 멍청한 자식아. (뭐에요 나 안 멍청해요...)
메인 함수에서 볼 수 있듯, Player의 인스턴스인 player의 메서드 UseItem으로 받고 있는 인자는 이전예제처럼 인터페이스로 선언한 객체가 아닌 Item클래스의 객체 item.
? 근데 그래서 뭐 어쩌라고? 라고 생각한다면 큰 오산이다.
여기서 눈여겨 볼 점은 인터페이스를 인자로 넘겨줄 수 있다는 점이다.
인터페이스를 상속받은 클래스를 넣어도 인터페이스만 알아서 넘겨준다는 점이다. 이 얼마나 사용성이 편리한가...!
지금은 예제이지만, 만약에 체력 물약 말고 마나 물약, 속도 물약, 힘의 물약, 귀환 주문서 등등 여러 아이템이 생길 것이다.
이런 아이템의 사용 인터페이스인 IUsable이 있다고 했을 때, Player의 사용 함수에서 IUsable인터페이스를 매개변수로 지정함으로서 모든 아이템의 사용을 단 한 줄의 코드로 요약할 수 있다는 것이다!!!!!!!!!!!!!!!!!!!!!!!!!! 이 얼마나....(이하생략)
각설하고 하나의 코드사용으로 수만개의 아이템에 대한 사용함수를 각각 들어가서 쉽게 자원 소모 없이 불러온다는 것은 아주 큰 의미가 있다.
인터페이스의 말 그대로 클래스를 합리적으로 이용하는 UI라는 느낌이다. 너희 너희 각각 다른 클래스들이 가진 공통된 의미의 기능인 요녀석 사용~(@^0^@)/ (하는 함수 작성..ㅇㅇ)
이것은 정말 혁신이다. 와 C#최고! 아니 근데 다른언어도 이 정도면 인터페이스 있겠는데? 예전에 배웠는데 큰 의미를 못느껴서 까먹었나 보다.
아무튼 요 인터페이스는 아주 자주 보게될 것만 같은 느낌이 든다. 개념을 확실히 알아두자.
뭐 그냥 추상클래스는 클래스를 생성할 때, 직접 사용자에게 정의를 맞기고 싶다는 생각이 들 때 사용하는 거 같고,
인터페이스는 여러 클래스에서 상속받는 동일한 형식의 객체가 있을 때 사용하면 된다는 거 같다. 인터페이스는 쉽게 클래스에 넣었다 떼었다 할 수 있고, 같은 인터페이스에 대해서 동일하게 취급하여 다룰 수 있기 때문에 이러한 기능들을 사용할 때 쓰면 유용하다.
이것도 공부해보니 매우 자주 쓸 친구이다.(확신)
다음은 코드로 정의를 보여주겠다.
enum MyEnum
{
Value1, // 값 지정을 해주지 않았다면 각각 1
Value2, // 2
Value3 // 3 으로 초기화 된다.
}
MyEnum myEnum = MyEnum.Value1;
enum MyEnum
{
Value1 = 10,
Value2, // 지정이 안 된 열거형은 이전 숫자 + 1로 초기화 된다. 여기선 11
Value3 = 20
}
int intValue = (int)MyEnum.Value1; // 열거형 값을 정수로 변환
MyEnum enumValue = (MyEnum)intValue; // 정수를 열거형으로 변환
switch(enumValue)
{
case MyEnum.Value1:
// Value1에 대한 처리
break;
case MyEnum.Value2:
// Value2에 대한 처리
break;
case MyEnum.Value3:
// Value3에 대한 처리
break;
default:
// 기본 처리
break;
}
열거형을 이용한 가독성을 높이는 코드 예제이다.
// 월 열거형
public enum Month
{
January = 1,
February,
March,
April,
May,
June,
July,
August,
September,
October,
November,
December
}
// 처리하는 함수
static void ProcessMonth(int month)
{
if (month >= (int)Month.January && month <= (int)Month.December)
{
Month selectedMonth = (Month)month;
Console.WriteLine("선택한 월은 {0}입니다.", selectedMonth);
// 월에 따른 처리 로직 추가
}
else
{
Console.WriteLine("올바른 월을 입력해주세요.");
}
}
// 실행 예제
static void Main()
{
int userInput = 7; // 사용자 입력 예시
ProcessMonth(userInput);
}
ProcessMonth의 if문에서 month를 열거형을 이용해 조건문을 작성함으로서 가독성을 높인다.
뭐하는 코드인지 바로바로 알아볼 수 있다.
month가 January부터 December 사이에 오면 if문을 실행시키는 구나~!
다음은 열거형의 게임 사용 사례 코드이다.
// 게임 상태
enum GameState
{
MainMenu,
Playing,
Paused,
GameOver
}
// 방향
enum Direction
{
Up,
Down,
Left,
Right
}
// 아이템 등급
enum ItemRarity
{
Common,
Uncommon,
Rare,
Epic
}
게임 개발에서 숫자 대신에 다음과 같은 열거형을 사용해서 코드를 작성하면 가독성을 심히 높일 수 있다. (음 음)
C#에서는 try-catch블록을 사용하여 예외처리 수행
try블록에서 예외 위험성이 있는 코드를 작성, catch블록에서 예외 처리
코드
try
{
// 예외가 발생할 수 있는 코드
}
catch (ExceptionType1 ex)
{
// ExceptionType1에 해당하는 예외 처리
}
catch (ExceptionType2 ex)
{
// ExceptionType2에 해당하는 예외 처리
}
finally
{
// 예외 발생 여부와 상관없이 항상 실행되는 코드
}
public class NegativeNumberException : Exception
{
//생성자. :base(message) 부분은 부모의 생성자. NegativeNumberException이 실행되면 부모의 생성자가 먼저 실행된다.
public NegativeNumberException(string message) : base(message)
{
}
}
try
{
int number = -10;
if (number < 0)
{
//throw로 의도적으로 예외 발생
throw new NegativeNumberException("음수는 처리할 수 없습니다.");
}
}
catch (NegativeNumberException ex)
{
Console.WriteLine(ex.Message);
}
catch (Exception ex)
{
Console.WriteLine("예외가 발생했습니다: " + ex.Message);
}
갑형은 존나쌔다. ? 밤 새더니 정신 나갔니?
값형과 참조형 사이의 변환을 나타냄(역시 형님들끼리 친하게 지내시네)
그냥 예제에 쓰여서 설명 들어감
예제 코드
// 박싱과 언박싱
int num1 = 10;
object obj = num1; // 박싱
int num2 = (int)obj; // 언박싱
Console.WriteLine("num1: " + num1); // 출력 결과: 10
Console.WriteLine("num2: " + num2); // 출력 결과: 10
List<object> myList = new List<object>();
// 박싱: 값 형식을 참조 형식으로 변환하여 리스트에 추가
int intValue = 10;
myList.Add(intValue); // int를 object로 박싱하여 추가
float floatValue = 3.14f;
myList.Add(floatValue); // float를 object로 박싱하여 추가
// 언박싱: 참조 형식을 값 형식으로 변환하여 사용
int value1 = (int)myList[0]; // object를 int로 언박싱
float value2 = (float)myList[1]; // object를 float로 언박싱
구현 코드
delegate void MyDelegate(string message);
static void Method1(string message)
{
Console.WriteLine("Method1: " + message);
}
static void Method2(string message)
{
Console.WriteLine("Method2: " + message);
}
class Program
{
static void Main()
{
// 델리게이트 인스턴스 생성 및 메서드 등록
MyDelegate myDelegate = Method1;
myDelegate += Method2;
// 델리게이트 호출
myDelegate("Hello!");
Console.ReadKey();
}
}
Method1: Hello!
Method2: Hello!
델리게이트로 선언한 것과 똑같은 반환Type과 똑같은 매개변수를 가지고 있는 Method1과 Method2를 만든다.
메인함수에서 델리게이트 타입으로 변수를 선언하고 델리게이트와 같은 모양의 함수(메서드)를 가진 함수를 등록한다.
+=을 사용해 메서드를 추가할 수 있다. 이렇게 되면 Method1이후 Method2가 실행된다.
// 델리게이트 선언
public delegate void EnemyAttackHandler(float damage);
// 적 클래스
public class Enemy
{
// 공격 이벤트
public event EnemyAttackHandler OnAttack;
// 적의 공격 메서드
public void Attack(float damage)
{
// 이벤트 호출
OnAttack?.Invoke(damage);
// null 조건부 연산자
// null 참조가 아닌 경우에만 멤버에 접근하거나 메서드를 호출
}
}
// 플레이어 클래스
public class Player
{
// 플레이어가 받은 데미지 처리 메서드
public void HandleDamage(float damage)
{
// 플레이어의 체력 감소 등의 처리 로직
Console.WriteLine("플레이어가 {0}의 데미지를 입었습니다.", damage);
}
}
// 게임 실행
static void Main()
{
// 적 객체 생성
Enemy enemy = new Enemy();
// 플레이어 객체 생성
Player player = new Player();
// 플레이어의 데미지 처리 메서드를 적의 공격 이벤트에 추가
enemy.OnAttack += player.HandleDamage;
// 적의 공격
enemy.Attack(10.0f);
}
플레이어가 10.0의 데미지를 입었습니다.
event 는 할당연산자(=)를 사용할 수 없고 +=과 -=을 이용해 조절해야 하며,
클래스 외부에서는 직접이벤트를 호출할 수 없다.(뭥말?)
아무튼, Enemy class에서 event형 델리게이트 OnAttack을 만들었는데, Attack함수에서 OnAttack이 널이 아닌 경우에만 Invoke(damage)를 실행한다. (OnAttack? >>> OnAttack이 null이 아니라면의 뜻인듯)
근데 아직까진 OnAttack은 비어있고 이제 메인함수에서 어떻게 OnAttack에 뭐가 들어가서 작동하는지 살펴보자.
메인함수에서 Enemy와 Player의 객체를 만들어준다.
Enemy의 객체 enemy에 enemy.OnAttack에 += player.HandleDamage를 하여 델리게이트 enemy.OnAttack이 player.HandleDamage메서드를 참조하게 됐다.
이제, enemy.Attack(10.0f)로 인해 OnAttack?.Invoke(damage)가 호출되고,
OnAttack이 null이 아니니 damage값인 10.0f가 OnAttack에 매개변수로 넘겨진다.
마지막으로, 델리게이트에 등록된 player.HandleDamage에 damage값인 10.0f가 인자로 전달되어,
최종적으로 "플레이어가 10.0의 데미지를 입었습니다."가 출력된다.
(parameter_list) => expression
//Calculate는 delegate타입
Calculate calc = (x, y) =>
{
return x + y;
};
//구현부가 한 줄이면 함수 구현부의 {}생략가능
Calculate calc = (x, y) => x + y;
// 델리게이트 선언
public delegate void GameEvent();
// 이벤트 매니저 클래스
public class EventManager
{
// 게임 시작 이벤트
public event GameEvent OnGameStart;
// 게임 종료 이벤트
public event GameEvent OnGameEnd;
// 게임 실행
public void RunGame()
{
// 게임 시작 이벤트 호출
OnGameStart?.Invoke();
// 게임 실행 로직
// 게임 종료 이벤트 호출
OnGameEnd?.Invoke();
}
}
// 게임 메시지 클래스
public class GameMessage
{
public void ShowMessage(string message)
{
Console.WriteLine(message);
}
}
// 게임 실행
static void Main()
{
// 이벤트 매니저 객체 생성
EventManager eventManager = new EventManager();
// 게임 메시지 객체 생성
GameMessage gameMessage = new GameMessage();
// 게임 시작 이벤트에 람다 식으로 메시지 출력 동작 등록
eventManager.OnGameStart += () => gameMessage.ShowMessage("게임이 시작됩니다.");
// 게임 종료 이벤트에 람다 식으로 메시지 출력 동작 등록
eventManager.OnGameEnd += () => gameMessage.ShowMessage("게임이 종료됩니다.");
// 게임 실행
eventManager.RunGame();
}
델리게이트를 대체하는 미리 정의된 제네릭 형식
Action은 값을 반환하지 않는 메서드(를 나타내는 델리게이트)
Action<int, string> 있으면 int와 string 둘 다 매개변수다. (반환값이 없으니까 void ~(int, string); 형식의 메서드 연결 가능)
Func 사용 예제
// Func를 사용하여 두 개의 정수를 더하는 메서드
int Add(int x, int y)
{
return x + y;
}
// Func를 이용한 메서드 호출
Func<int, int, int> addFunc = Add;
int result = addFunc(3, 5);
Console.WriteLine("결과: " + result);
// Action을 사용하여 문자열을 출력하는 메서드
void PrintMessage(string message)
{
Console.WriteLine(message);
}
// Action을 이용한 메서드 호출
Action<string> printAction = PrintMessage;
printAction("Hello, World!");
미리 정의된 형식을 이용하여 델리게이트를 좀 더 간편하게 쓸 수 있다.
Func과 Action은 그냥 메서드들을 저장하는 형식인 델리게이트를 미리 지정된 형식과 타입으로 쉽게 쓸 수 있게 만든 것이다.
그니까 얘네들도 그냥 메서드 저장하는 거다. 델리쨩처럼. 예? 델리게이트처럼. 델리키 생각나네
근데 쿼리가 뭔데요 ㅠㅠ 그런거 몰라요. 기억안나...
쿼리란? 데이터베이스에 정보를 검색하기 위해 요청하는 것.
영어로 '질의' 라는 뜻을 가지고 있는데, 데이터베이스에게 내가 원하는 것을 요구하기 위한 것이다.
var result = from 변수 in 데이터소스
[where 조건식]
[orderby 정렬식 [, 정렬식...]]
[select 식];
var
는 변수의 자료형을 자동으로 맞춘다.from
절에서 데이터소스를 지정(질문을 던질 데이터집합)where
절은 선택적으로 사용. 조건식을 지정하여 원하는 데이터를 필터링.orderby
절은 선택적으로 사용. 정렬 방식을 지정.select
절은 선택적으로 사용. 가져올 데이터를 지정.// 데이터 소스 정의 (컬렉션)
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
// 쿼리 작성 (선언적인 구문)
var evenNumbers = from num in numbers
where num % 2 == 0
select num;
// 쿼리 실행 및 결과 처리
foreach (var num in evenNumbers)
{
Console.WriteLine(num);
}
?
연산자를 사용하여 할당 가능. int?
=> Nullable// Nullable 형식 변수 선언
int? nullableInt = null;
double? nullableDouble = 3.14;
bool? nullableBool = true;
// 값 할당 및 접근
nullableInt = 10;
int intValue = nullableInt.Value;
// null 값 검사
if (nullableDouble.HasValue)
{
Console.WriteLine("nullableDouble 값: " + nullableDouble.Value);
}
else
{
Console.WriteLine("nullableDouble은 null입니다.");
}
// null 병합 연산자 사용
// nullableInt ?? 0과 같이 사용되며, nullableInt가 null이면 0을 반환합니다.
int nonNullableInt = nullableInt ?? 0;
Console.WriteLine("nonNullableInt 값: " + nonNullableInt);
문자열 빌더(StringBuilder) 쓰는 이유?
"A" + "B" + "C"를 사용하면 "A" + "B" 에서 "A", "B", "A" + "B"의 결과인 "AB"가 메모리에 저장을 해 놓는다. 또 "AB" + "C" 에서 "AB", "C", "ABC"를 저장하게 된다. 그러면 "A" + "B" + "C"를 하기 위해서, "A", "B", "C", "AB", "ABC"가 메모리에 저장되기 때문에 많은양의 메모리를 잡아먹는다.
그렇기 때문에 문자열 빌더나 String포멧을 사용하는 것을 추천한다.
StringBuilder sb = new StringBuilder();
// 문자열 추가
sb.Append("Hello");
sb.Append(" ");
sb.Append("World");
// 문자열 삽입
sb.Insert(5, ", ");
// 문자열 치환
sb.Replace("World", "C#");
// 문자열 삭제
sb.Remove(5, 2);
// 완성된 문자열 출력
string result = sb.ToString();
Console.WriteLine(result);
HelloC#