- 델리게이트 요약
- 델리게이트(delegate)는 메서드를 참조하는 타입입니다.
- 다른 프로그래밍 언어에서는 함수 포인터라는 용어를 사용하기도 합니다.
- 델리게이트를 이용하면 메서드를 매개변수로 전달하거나 변수에 할당할 수 있습니다.
C#과 같은 프로그래밍 언어에서 매우 중요한 개념이며 다음과 같은 특징을 가진다.
함수 포인터: 델리게이트는 기본적으로 함수에 대한 참조, 즉 함수 포인터와 비슷합니다. 이를 통해 함수를 다른 함수나 메서드의 인자로 전달하거나, 변수에 할당할 수 있습니다.
타입 안전성: 델리게이트는 타입 안전한 방법으로 함수를 다룹니다. 즉, 델리게이트는 특정 시그니처(반환 타입과 매개변수)를 가진 함수만 참조할 수 있습니다.
이벤트 처리: 델리게이트는 이벤트 기반 프로그래밍에서 중요한 역할을 합니다. 이벤트가 발생했을 때 호출되어야 하는 메서드를 지정하는 데 사용됩니다.
익명 메서드와 람다 표현식과의 결합: 델리게이트는 익명 메서드나 람다 표현식과 결합하여 사용될 수 있어, 코드를 간결하고 유연하게 작성할 수 있도록 도와줍니다.
유니티 게임 개발에서의 예를 들면, 플레이어의 행동(예: 점프, 공격 등)에 대한 반응으로 특정 함수를 호출하고 싶을 때 델리게이트를 사용할 수 있습니다. 이를 통해, 플레이어의 행동에 따라 다양한 효과나 게임 로직을 유연하게 연결할 수 있습니다.
public delegate void MyDelegate(string message); // 델리게이트 정의
public class DelegateExample
{
public void ShowMessage(string message)
{
Console.WriteLine(message);
}
public void Execute()
{
MyDelegate del = ShowMessage; // 델리게이트 인스턴스화
del("Hello, World!"); // 델리게이트를 통해 메서드 호출
}
}
이 코드에서 MyDelegate는 문자열을 매개변수로 받는 메서드를 참조할 수 있는 델리게이트 타입입니다. Execute 메서드에서 ShowMessage 메서드를 MyDelegate 타입의 del 변수에 할당하고, 이를 통해 메서드를 호출합니다.
2) 델리게이트 구현
메서드 등록해서 사용하기
delegate int Calculate(int x, int y);
static int Add(int x, int y)
{
return x + y;
}
class Program
{
static void Main()
{
// 메서드 등록
Calculate calc = Add;
// 델리게이트 사용
int result = calc(3, 5);
Console.WriteLine("결과: " + result);
}
}
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();
}
}
// 델리게이트 선언
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);
}
이 코드는 C#의 델리게이트와 이벤트를 활용하여 게임 내에서 적(Enemy)이 플레이어(Player)에게 공격을 가하는 상황을 모델링하고 있습니다.
적 클래스 (Enemy)
Enemy 클래스 내에 EnemyAttackHandler 델리게이트 타입을 사용하는 OnAttack 이벤트가 선언되어 있습니다.
Attack 메서드는 적이 공격하는 로직을 구현하며, 공격 시 OnAttack 이벤트를 트리거합니다.
OnAttack?.Invoke(damage); 구문은 OnAttack 이벤트에 등록된 메서드가 있을 경우에만 호출하도록 합니다. 이는 null 조건부 연산자를 사용한 안전한 이벤트 호출 방법입니다.
플레이어 클래스 (Player)
HandleDamage 메서드는 플레이어가 데미지를 받았을 때의 처리를 담당합니다.
이 메서드는 EnemyAttackHandler 델리게이트의 시그니처와 일치하므로, OnAttack 이벤트에 연결될 수 있습니다.
게임 실행 로직
Main 메서드에서는 Enemy와 Player 객체를 생성하고, Player의 HandleDamage 메서드를 Enemy의 OnAttack 이벤트에 등록합니다.
적의 Attack 메서드를 호출하면, 이벤트가 트리거되고 Player의 HandleDamage 메서드가 실행됩니다.
이벤트 특징:
event 키워드는 델리게이트를 캡슐화하여 외부에서의 직접적인 접근을 제한합니다.
클래스 외부에서는 이벤트에 메서드를 추가(+=)하거나 제거(-=)할 수 있지만, 직접 호출하거나 할당(=)할 수는 없습니다. 이는 이벤트의 발생을 클래스 내부 로직에만 한정시켜, 외부에서의 무분별한 이벤트 발생을 방지합니다.
람다 요약
- 람다(lambda)는 익명 메서드를 만드는 방법입니다.
- 람다를 사용하면 메서드의 이름 없이 메서드를 만들 수 있습니다.
- 람다는 델리게이트를 사용하여 변수에 할당하거나, 메서드의 매개변수로 전달할 수 있습니다.
람다(lambda) 표현식은 C#에서 매우 유용한 기능으로, 익명 메서드(이름이 없는 메서드)를 생성하는 간결한 방법을 제공합니다. 람다 표현식은 특히 LINQ 쿼리, 이벤트 처리, 델리게이트 할당 등에 널리 사용됩니다.
(input-parameters) => expression
// 인자 없는 람다 표현식
() => Console.WriteLine("Hello World");
// 한 개의 인자를 받는 람다 표현식
x => x * x;
// 여러 개의 인자를 받는 람다 표현식
(x, y) => x + y;
1) 형식
(parameter_list) => expression
2) 정의하기
Calculate calc = (x, y) =>
{
return x + y;
};
Calculate calc = (x, y) => x + y;
3) 사용예제
using System;
// 델리게이트 선언
delegate void MyDelegate(string message);
class Program
{
static void Main()
{
// 델리게이트 인스턴스 생성 및 람다식 할당
MyDelegate myDelegate = (message) =>
{
Console.WriteLine("람다식을 통해 전달된 메시지: " + message);
};
// 델리게이트 호출
myDelegate("안녕하세요!");
Console.ReadKey();
}
}
// 델리게이트 선언
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();
}
예시 코드)
using System;
using System.Collections.Generic;
using UnityEngine;
public class LambdaExample : MonoBehaviour
{
void Start()
{
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
// List에서 홀수만 필터링
List<int> oddNumbers = numbers.FindAll(n => n % 2 != 0);
// 홀수 출력
oddNumbers.ForEach(n => Debug.Log(n));
}
}
이 예제에서 FindAll 메서드는 람다 표현식을 사용하여 홀수를 필터링합니다. 람다 표현식 n => n % 2 != 0은 각 요소 n이 홀수인지를 평가합니다. ForEach 메서드는 또 다른 람다 표현식을 사용하여 필터링된 홀수를 출력합니다.
람다 표현식은 코드를 간결하고 읽기 쉽게 만들어 줄 뿐만 아니라, 함수형 프로그래밍 스타일을 C#에 통합하는 데 중요한 역할을 합니다. 유니티 개발에서도 람다를 활용하면 이벤트 처리, 데이터 처리 등 다양한 곳에서 코드를 더욱 효율적으로 작성할 수 있습니다.
Func과 Action은 델리게이트를 대체하는 미리 정의된 제네릭 형식입니다.
(델리게이트 대신 더 많이 쓰는 편)
Func
과 Action
은 델리게이트
를 대체하는 미리 정의된 제네릭 형식입니다.
Func
는 값을 반환하는 메서드를 나타내는 델리게이트입니다. 마지막 제네릭 형식 매개변수는 반환 타입을 나타냅니다. 예를 들어, Func<int, string>
는 int
를 입력으로 받아 string
을 반환하는 메서드를 나타냅니다.
Action
은 값을 반환하지 않는 메서드를 나타내는 델리게이트입니다. Action
은 매개변수를 받아들이지만, 반환 타입이 없습니다. 예를 들어, Action<int, string>
은 int
와 string
을 입력으로 받고, 아무런 값을 반환하지 않는 메서드를 나타냅니다.
Func
및 Action
은 제네릭 형식으로 미리 정의되어 있어 매개변수와 반환 타입을 간결하게 표현할 수 있습니다.
// 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!");
아래 예제는 게임 개발에서 캐릭터 상태 변경에 대한 반응을 구현하는 방법을 보여줍니다.
Action과 람다 표현식을 사용하면, 이벤트나 상태 변경에 대한 로직을 유연하고 간결하게 작성할 수 있습니다.
// 게임 캐릭터 클래스
class GameCharacter
{
private Action<float> healthChangedCallback;
private float health;
public float Health
{
get { return health; }
set
{
health = value;
healthChangedCallback?.Invoke(health);
}
}
public void SetHealthChangedCallback(Action<float> callback)
{
healthChangedCallback = callback;
}
}
// 게임 캐릭터 생성 및 상태 변경 감지
GameCharacter character = new GameCharacter();
character.SetHealthChangedCallback(health =>
{
if (health <= 0)
{
Console.WriteLine("캐릭터 사망!");
}
});
// 캐릭터의 체력 변경
character.Health = 0;
여기서 Action 델리게이트는 매개변수로 float 값을 받고 반환 값이 없는 메서드를 참조합니다. 이 코드는 게임 캐릭터의 체력이 변경될 때마다 특정한 동작을 실행하는 메커니즘을 구현하고 있습니다.
GameCharacter 클래스
Health 프로퍼티는 게임 캐릭터의 체력을 나타냅니다. 체력 값이 설정될 때마다 healthChangedCallback을 호출하여 체력 변경을 알립니다.
SetHealthChangedCallback 메서드
이 메서드는 외부에서 healthChangedCallback을 설정할 수 있도록 합니다. 이를 통해 체력이 변경될 때 실행될 콜백 함수를 지정할 수 있습니다.
캐릭터 생성 및 콜백 설정
GameCharacter 인스턴스를 생성하고, SetHealthChangedCallback 메서드를 통해 체력 변경 시 호출될 콜백 함수를 람다 표현식을 사용하여 정의합니다.
이 람다 표현식은 캐릭터의 체력이 0 이하로 떨어질 때 "캐릭터 사망!"을 출력합니다.
캐릭터 체력 변경
character.Health = 0; 구문은 캐릭터의 체력을 0으로 설정합니다. 이에 따라 콜백 함수가 호출되고, 캐릭터의 사망 메시지가 출력됩니다.
- 1) LINQ란?
- .NET 프레임워크에서 제공되는 쿼리 언어 확장
- 데이터 소스(예: 컬렉션, 데이터베이스, XML 문서 등)에서 데이터를 쿼리하고 조작하는데 사용됩니다.
- 데이터베이스 쿼리와 유사한 방식으로 데이터를 필터링, 정렬, 그룹화, 조인 등 다양한 작업을 수행할 수 있습니다.
- LINQ는 객체, 데이터베이스, XML 문서 등 다양한 데이터 소스를 지원합니다.
LINQ(Language Integrated Query)는 C# 및 .NET 프레임워크에서 데이터를 쿼리하는 강력한 기능입니다.
LINQ는 데이터 소스에 관계없이 일관된 쿼리 언어를 제공합니다.
데이터 소스가 컬렉션, SQL 데이터베이스, XML 문서, 데이터 세트 등 무엇이든 간에, LINQ를 사용하여 데이터를 쉽고 간결하게 조회하고 조작할 수 있습니다.
통합된 쿼리 언어: LINQ는 SQL과 유사한 쿼리 구문을 C# 코드 내에서 직접 사용할 수 있게 해줍니다.
다양한 데이터 소스 처리: LINQ는 다양한 유형의 데이터 소스에 적용될 수 있습니다. 예를 들어, LINQ to Objects는 메모리 내 컬렉션에, LINQ to SQL은 SQL 데이터베이스에, LINQ to XML은 XML 문서에 적용됩니다.
객체 지향적 접근: LINQ 쿼리의 결과는 객체의 컬렉션으로 반환됩니다. 이를 통해, 쿼리 결과를 객체와 같은 방식으로 쉽게 다룰 수 있습니다.
컴파일 시간 검증: LINQ 쿼리는 컴파일 시간에 타입 검사가 이루어지므로, 런타임 오류를 줄일 수 있습니다.
읽기 쉽고 유지보수하기 쉬운 코드: LINQ는 쿼리를 명확하고 선언적인 방식으로 작성할 수 있게 해, 코드의 가독성과 유지보수성을 향상시킵니다.
var result = from 변수 in 데이터소스
[where 조건식]
[orderby 정렬식 [, 정렬식...]]
[select 식];
var
키워드는 결과 값의 자료형을 자동으로 추론합니다.from
절에서는 데이터 소스를 지정합니다.where
절은 선택적으로 사용하며, 조건식을 지정하여 데이터를 필터링합니다.orderby
절은 선택적으로 사용하며, 정렬 방식을 지정합니다.select
절은 선택적으로 사용하며, 조회할 데이터를 지정합니다. 예제1)
// 데이터 소스 정의 (컬렉션)
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);
}
C#의 LINQ(Language Integrated Query) 기능을 활용하여 컬렉션 내의 짝수를 찾는 간단한 예제
LINQ의 선언적 쿼리 구문을 사용하여 numbers 리스트에서 짝수만을 선택하고 출력합니다.
List numbers = new List { 1, 2, 3, 4, 5 };
여기서 numbers는 int 타입의 리스트로, LINQ 쿼리의 대상이 되는 데이터 소스입니다.
from num in numbers: numbers 컬렉션의 각 요소를 num이라는 변수로 순회합니다.
where num % 2 == 0: num % 2 == 0 조건을 만족하는 요소, 즉 짝수를 필터링합니다.
select num: 필터링된 요소를 선택하여 새로운 컬렉션을 만듭니다.
이 쿼리는 numbers 리스트에서 짝수만을 선택하는 LINQ 쿼리입니다.
LINQ 쿼리의 결과인 evenNumbers를 foreach 루프를 통해 순회하면서 각 요소를 출력합니다.
이 부분에서 쿼리가 실제로 실행됩니다. LINQ 쿼리는 대개 "지연 실행"(deferred execution) 방식을 따릅니다.
즉, 쿼리 자체는 선언 시에 실행되지 않고, 실제로 결과가 필요한 시점(이 경우 foreach 루프)에 실행됩니다.
예제2)
using System;
using System.Linq;
using System.Collections.Generic;
class LinqExample
{
static void Main()
{
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6 };
// LINQ를 사용하여 짝수만 필터링
var evenNumbers = from num in numbers
where num % 2 == 0
select num;
foreach (var n in evenNumbers)
{
Console.WriteLine(n);
}
}
}
이 예제에서는 LINQ를 사용하여 리스트 numbers에서 짝수만을 선택(select)하고 있습니다. from 절은 컬렉션을 반복하고, where 절은 조건을 기반으로 필터링을 수행합니다. 결과적으로, 이 코드는 리스트에서 짝수만 추출하여 출력합니다.
유니티
게임 개발에서도 LINQ는 데이터 처리
, 특히 복잡한 컬렉션
조작이 필요할 때 유용하게 사용됩니다. 예를 들어, 특정 조건을 만족하는 게임 오브젝트를 찾거나, 데이터를 정렬하고 그룹화하는 데에 LINQ 쿼리를 활용할 수 있습니다.
1) null
null은 "아무것도 없음"을 의미한다.
참조형 변수가 어떠한 객체를 참조하지 않을 때 사용됩니다.
예시: string str = null; 여기서 str은 어떤 문자열도 참조하지 않습니다.
2) Nullable 란?
Nullable은 C#에서 null 값을 가질 수 있는 값형에 대한 특별한 형식입니다.
기본적으로 값형은 null을 허용하지 않습니다.
값형 변수에 null 값을 지정할 수 있는 방법을 제공하여 값형이나 구조체를 사용하는 프로그램에서 null 상태를 나타낼 수 있습니다. 주로 값형 변수가 null인지 아닌지를 확인하고 처리해야 할 때 유용합니다.
형식은 ?
연산자를 사용하여 선언됩니다. 예를 들어, int?
는 int 형식에 null을 할당할 수 있는 Nullable 형식을 나타냅니다.
예시: int? num = null; 여기서 num은 int 값형에 null을 할당할 수 있습니다.
// 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);
유니티 게임 개발에서 Nullable은 특정 컴포넌트나 값이 아직 할당되지 않았거나 더 이상 유효하지 않은 경우를 표현하는 데 유용합니다.
예를 들어, 플레이어의 점수나 캐릭터의 체력이 초기화되지 않았을 때 null로 표현할 수 있습니다. 이를 통해 게임 로직에서 유효하지 않은 상태를 쉽게 처리하고 오류를 방지할 수 있습니다.
public class Player : MonoBehaviour
{
// Nullable을 사용하여 초기 점수를 null로 설정
int? score = null;
void Start()
{
// 점수가 설정되지 않았다면 특정 로직 실행
if (score == null)
{
Debug.Log("점수가 아직 설정되지 않았습니다.");
}
}
}
이 코드에서 score 변수는 Nullable int로 선언되어, 초기에는 null 값을 가집니다. 이를 통해 게임이 시작될 때 플레이어의 점수가 아직 설정되지 않았음을 표현하고, 이에 따른 특정 로직을 실행할 수 있습니다.
StringBuilder
는 C#에서 문자열을 조작할 때 사용되는 클래스입니다. 특히 문자열을 반복적으로 수정할 때 StringBuilder를 사용하면, 일반 문자열(string) 조작에 비해 성능상의 이점이 있습니다.
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);
유니티 게임 개발에서 StringBuilder는 주로 게임 내 텍스트 정보를 동적으로 생성하거나 수정할 때 사용됩니다. 예를 들어, 점수판, 인벤토리 설명, 게임 로그 등의 텍스트를 구성할 때 StringBuilder를 활용하면 성능상 이점을 얻을 수 있습니다.
using UnityEngine;
using System.Text;
public class GameStatus : MonoBehaviour
{
void UpdateScore(int score, int lives)
{
StringBuilder sb = new StringBuilder();
sb.Append("Score: ").Append(score).Append(" - Lives: ").Append(lives);
// 여기서 UpdateScore 메서드는 점수판 UI 텍스트를 업데이트할 수 있습니다.
UpdateScoreText(sb.ToString());
}
void UpdateScoreText(string text)
{
// 점수판 UI 업데이트 로직
Debug.Log(text);
}
}
이 예제에서는 점수와 생명 수를 표시하는 문자열을 StringBuilder를 사용하여 구성하고 있습니다. StringBuilder를 사용함으로써 문자열 연결이나 수정 시 발생할 수 있는 성능 저하를 최소화할 수 있습니다.