C# 종합 문법 4 - 2

김정환·2024년 9월 20일
0

델리게이트 Delegate

  • 메소드를 참조하는 타입 = 메서드를 저장하는 타입
  • 다른 언어에선 함수 포인터와 유사함

구현1

delegate int Calculate(int x, int y);

// 델리게이트로 선언한 형식과 동일해야한다.
static int Add(int x, int y){
	retrun x + y;
}

void Main(){
	// 일반 변수 선언하듯 델리게이트 선언하고 함수 할당
	Calculate calc = Add;
    
    // 사용
    int result = calc(2, 2);
}

사용하는 이유?

실제로는 메서드에 접근하기 쉽지 않은 환경일 때, 메서드를 연결하여 바로 사용하기 위함.

구현2

여러 메서드 등록하기

delegate void MyDelegate(string msg);

static void Method1(string message){
	Console.WriteLine("Method1 : " + message);
}

static void Method2(string message){
	Console.WriteLine("Method2 : " + message);
}


void Main(){
	// 델리게이트 인스턴스 생성 및 메서드 등록
	MyDelegate myDelegate = Method1;
    myDelegate += Method2;
    
    // 델리게이트 호출
   	myDelegate("Hello!");
    // 결과
    // "Method1 : Hello"
    // "Method2 : Hello"
}

사용 예시

  • 공격 콜백
    • event를 붙여 사용한 예시
    • event는 할당 연산자( = )를 사용할 수 없고, 외부에서 직접 호출할 수 없다.
public delegate void EnemyAttackHandler(float damage);

public class Enemy
{
    // 공격 이벤트
    public event EnemyAttackHandler OnAttack; 
    // attack이 발생할 때 해주어야할 메서드들을 모두 예약을 걸어주었다고 생각하면 편함

    // 적의 공격 메서드
    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);
}

* 실제에선 델리게이트보다 좀 더 다른 것들과 함께 많이 씀.


람다 Lambda

  • 익명 메서드

    • 이름없이 메서드를 만들 수 있음.
  • 참조만으로 컨트롤

  • 형태

(parameter_list) => expression(코드)
  • 정의
// Calculate은 델리게이트
// 여러줄 구현
Calculate calc = (x, y) => 
{	
      return x + y;
};

// 한줄
Calculate calc = (x, y) => x + y;
using System;

// 델리게이트 선언
delegate void MyDelegate(string message);

class Program
{
    static void Main()
    {
        // 델리게이트 인스턴스 생성 및 람다식 할당
        MyDelegate myDelegate = (message) =>
        {
            Console.WriteLine("람다식을 통해 전달된 메시지: " + message);
        };

        // 델리게이트 호출
        myDelegate("안녕하세요!");

        Console.ReadKey();
    }
}
  • 위와 같이 람다를 쓸 때는 delegate와 함께 쓰는 경우가 많음.

람다를 쓰는 이유

  • 간단한 처리를 위해 함수를 만들고 연결하는 과정보다
    필요한 기능을 위해 코드만 던져주는 게 편리해서.
  • 더 구체적이고 확장된다면 메서드로 빼주는게 깔끔함.
  • 국지적으로 간단하게 써야한다면 람다가 편함.
// 델리게이트 선언
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();
}

Func과 Action

  • 결국엔 델리게이트
  • 단, 미리 제너릭으로 정의가 되어있는 델리게이트
  • 미리 정의되어 있는 만큼 간결하고 안정적

Func

  • 반환값이 있는 델리게이트
Func<int, string>
int : 매개변수
string : 반환값

string ~~~(int num){} 메서드를 연결할 수 있음.

Action

  • 반환값 없이 실행만 되는 델리게이트
Action<int, string>
int : 매개변수1
string : 매개변수2

void ~~~(int, string){} 형태의 메서드를 연결할 수 있음.

예시

Func

static int Add(int x, int y){
	return x + y;
}

void Main(){
	Func<int, int, int> addFunc = Add;
    int result = addFunc(3, 5); // 8
}

Action

static void PrintMessage(string message)
{
    Console.WriteLine(message);
}

void Main(){
	Action<string> printAction = PrintMessage;
    printAction("Hello, World.");
}

활용

// 게임 캐릭터 클래스
class GameCharacter
{
    private Action<float> healthChangedCallback;

    private float health;

    public float Health
    {
        get { return health; }
        set
        {
            health = value;
            healthChangedCallback?.Invoke(health);
            // health 변경 때마다 무조건 실행.
            // health 변경에 대한 콜백을 구성한 것.
        }
    }

    public void SetHealthChangedCallback(Action<float> callback)
    {
        healthChangedCallback = callback;
    }
}

// 게임 캐릭터 생성 및 상태 변경 감지
GameCharacter character = new GameCharacter();
//액션에 람다 할당
character.SetHealthChangedCallback(health =>
{
    if (health <= 0)
    {
        Console.WriteLine("캐릭터 사망!");
    }
});

// 캐릭터의 체력 변경
character.Health = 0;

LINQ

  • .NET 프레임워크에서 제공하는 쿼리 언어의 확장
  • 코드에서 컬렉션, 배열, DB, XML 등에 쿼리문을 실행할 수 있는 것
  • DB 쿼리와 유사한 기능 수행
    * 필터링, 정렬, 그룹화, 조인 등 수행 가능
    • 단, 데이터가 많아지고 필터링 조건이 많아지면 성능에 영향을 많이 끼침
    • 적절한 조건 사용과 미리 데이터를 정제하고 쿼리도 정리해서 사용.

구조

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);
}

고급 자료형 및 기능

Nullable 형

  • null = 아무것도 없다
  • 참조형 변수가 어떠한 객체를 참조하지 않을 때 사용

Nullable

  • C#에서 null 값을 가질 수 있는 값형에 대한 특별한 형식
  • 기본적으로 값형은 null을 허용하지 않음.
  • 값형(구조체, 일반 변수)에 null 값을 지정할 수 있게 함.
    • 주로 값형 변수가 null인지 아닌지 구분할 때 사용.
  • 형식은 ? 연산자를 사용해서 선언

예시

// 선언
int? nullableInt = null; // Nullable<int> 형식

// 할당 및 접근
nullableInt = 10; // 일반적으로 사용하듯이 접근
int intVal = nullableInt.value; // 또는 .value로 접근

// null 검사
if (nullableInt.HasValue){

}

// null 병합 연산자 사용 (자주 쓰진 않지만 알아두면 좋음)
// nullableInt ?? 0과 같이 사용되며, nullableInt가 null이면 0을 반환합니다.
int nonNullableInt = nullableInt ?? 0;
Console.WriteLine("nonNullableInt 값: " + nonNullableInt);

문자열 빌더 StringBuilder

  • 문자열들을 내부 버퍼에 넣어뒀다가 필요하면 문자열로 바꿔주는 것.
  1. 문자열 조작 기능
    Append() : 추가, Insert() : 삽입, Replace() : 대체, Remove() : 삭제 등등
  2. 가변성
    내부 버퍼를 사용해서 안에서 크기를 동적으로 할당할 수 있음.
  3. 효율적인 메모리 관리
    일반적인 문자열 처리는 (특히 "a"+"b"+"c") 매우 메모리를 많이 사용.
    이유는 문자열이 들어오면 다음에 사용할 것을 예상해서 메모리에 계속 상주시키는 것.
    더하기로 진행하면 결과물까지 문자열을 계속 상주시킴.

주요 기능

  • Append: 문자열을 뒤에 추가합니다.
  • Insert: 문자열을 지정한 위치에 삽입합니다.
  • Remove: 지정한 위치에서 문자열을 제거합니다.
  • Replace: 문자열의 일부를 다른 문자열로 대체합니다.
  • Clear: StringBuilder의 내용을 모두 지웁니다.
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);

주의

C#에서 제공한다고 해서 Unity C#에서도 지원하는 것은 아님
지금까지 내용은 대체로 유니티에서도 지원하지만 최신기능은 그렇지 않을 수도 있으니 유의

profile
만성피로 개발자

0개의 댓글