C#문법 - 4

이준호·2023년 11월 10일
0

C#문법 종합반 4주차



📌 인터페이스와 열거형

01. 다중 상속을 사용하지 않는 이유

  • 다이아몬드 문제(Diamond Problem) :
    다중 상속을 허용하면 한 클래스가 두 개 이상의 부모 클래스로부터 동일한 멤버를 상속받을수 있다. 이 경우, 같은 이름의 멤버를 가지고 있을 때 어떤 부모 클래스의 멤버를 사용해야 하는지 모호해진다. 이런 모호성을 해결하기 위한 규칙이 필요하게 되는데, 이로 인해 코드가 복잡해지고 가독성이 저하될 수 있다.

  • 설계의 복잡성 증가 :
    다중 상속을 허용하면 클래스 간의 관계가 복잡해진다. 클래스가 다중 상속을 받을 경우, 어떤 클래스로부터 어떤 멤버를 상속받을지 결정해야 한다. 이로 인해 클라스 간의 상속 관계를 파악하기 어려워지고 코드의 유지 보수성이 저하될 수 있다.

  • 이름 충돌과 충돌 해결의 어려운 :
    다중 상속을 허용하면 여러 부모 클래스로부터 상속받은 멤버들의 이름이 충돌할 수 있다. 이러한 충돌을 해결하기 위해 충돌하는 멤버를 재정의해야 하거나 명시적으로 부모 클래스를 지정해야 할 수 있다. 이는 코드의 복잡성을 증가시키고 오류 발생 가능성을 높인다.

  • 설계의 일관성과 단순성 유지 :
    C#은 단일 상속을 통해 설계의 일관성과 단순성을 유지하고자 한다. 단일 상속을 통해 클래스 간의 관계를 명확하게 만들고 코드의 가독성과 이해도를 높일 수 있다. 또한 인터페이스를 사용하여 다중 상속이 필요한 경우에도 유사한 기능을 구현할 수 있다.






02. 인터페이스를 사용하는 이유

  • 코드의 재사용성 :
    인터페이스를 사용하면 다른 클래스에서 해당 인터페이스를 구현하여 동일한 기능을 공유할 수 있다. 인터페이스를 통해 다양한 클래스가 동일한 동작을 수행할 수 있으므로 코드의 재사용성이 향상된다.

  • 다중 상속 제공 :
    C#에서는 클래스는 단일 상속만 지원하지만, 인터페이스는 다중 상속을 지원한다. 클래스가 여러 인터페이스를 구현함으로써 여러 개의 기능을 조합할 수 있다. 다중 상속을 통해 클래스는 더 다양한 동작을 수행할 수 있다.

  • 유연한 설계 :
    인터페이스를 사용하면 클래스와 인터페이스 간에 느슨한 결합을 형성할 수 있다. 클래스는 인터페이스를 구현하기만 하면 되므로, 클래스의 내부 구현에 대한 변경 없이 인터페이스의 동작을 변경하거나 새로운 인터페이스를 추가할 수 있다. 이는 유연하고 확장 가능한 소프트웨어 설계를 가능하게 한다.






03. 인터페이스(Interface)

1) 인터페이스 특징

  • 인터페이스란 클래스가 구현해야 하는 멤버들을 정의하는 것이다.

  • 인터페이스는 클래스의 일종이 아니며, 클래스에 대한 제약 조건을 명시하는 것이다.

  • 클래스가 인터페이스를 구현할 경우, 모든 인터페이스 멤버를 구현해야 한다.

  • 인터페이스는 다중 상속을 지원한다.

2) 인터페이스 구현

  • 인터페이스 및 멤버 정의하기
interface IMyInterface
{
    void Method1();
    int Method2(string str);
}
  • 인터페이스 구현하기
class MyClass : IMyInterface
{
    public void Method1()
    {
        // 구현
    }

    public int Method2(string str)
    {
        // 구현
        return 0;
    }
}

3) 사용 예제

  1. 인터페이스 구현해보기
  • 인터페이스 정의하기 :
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); // 적 이동
  1. 아이템 사용 구현 예제
// 아이템을 사용할 수 있는 인터페이스
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);
}
  1. 다중 상속 구현 예제
// 인터페이스 1
public interface IItemPickable
{
    void PickUp();
}

// 인터페이스 2
public interface IDroppable
{
    void Drop();
}

// 아이템 클래스
public class Item : IItemPickable, IDroppable
{
    public string Name { get; set; }

    public void PickUp()
    {
        Console.WriteLine("아이템 {0}을 주웠습니다.", Name);
    }

    public void Drop()
    {
        Console.WriteLine("아이템 {0}을 버렸습니다.", Name);
    }
}

// 플레이어 클래스
public class Player
{
    public void InteractWithItem(IItemPickable item)
    {
        item.PickUp();
    }

    public void DropItem(IDroppable item)
    {
        item.Drop();
    }
}

// 게임 실행
static void Main()
{
    Player player = new Player();
    Item item = new Item { Name = "Sword" };

    // 아이템 주울 수 있음
    player.InteractWithItem(item);

    // 아이템 버릴 수 있음
    player.DropItem(item);
}

4) 인터페이스 vs 추상클래스

인터페이스의 특징과 장단점 :

  • 인터페이스는 추상적인 동작만 정의하고, 구현을 갖지 않는다.

  • 다중 상속이 가능하며, 여러 클래스가 동일한 인터페이스를 구현할 수 있다.

  • 클래스들 간의 결합도를 낮추고, 유연한 상호작용을 가능하게 한다.

  • 코드의 재사용성과 확장성을 향상시킨다.

  • 단점으로는 인터페이스를 구현하는 클래스가 모든 동작을 구현해야 한다는 의무를 가지기 때문에 작업량이 증가할 수 있다.


추상 클래스의 특징과 장단점 :

  • 추상 클래스는 일부 동작의 구현을 가지며, 추상 메서드를 포함할 수 있다.

  • 단일 상속만 가능하며, 다른 클래스와 함께 상속 계층 구조를 형성할 수 있다.

  • 공통된 동작을 추상화하여 코드의 중복을 방지하고, 확장성을 제공한다.

  • 구현된 동작을 가지고 있기 때문에, 하위 클래스에서 재정의하지 않아도 될 경우 유용하다.

  • 단점으로는 다중 상속이 불가능하고, 상속을 통해 밀접하게 결합된 클래스들을 형성하므로 유연성이 제한될 수 있다.






04. 열거형(Enums)

1) 사용하는 이유

  • 가독성 : 열거형을 사용하면 일련의 연관된 상수들을 명명할 수 있다. 이를 통해 코드의 가독성이 향상되고, 상수를 사용할 때 실수로 잘못된 값을 할당하는 것을 방지할 수 있다.

  • 자기 문서화(Self-documenting) : 열거형은 의미 있는 이름을 사용하여 상수를 명명할 수 있다. 이를 통해 코드의 가독성이 향상되며, 상수의 의미를 명확하게 설명할 수 있다.

  • 스위치 문과의 호환성 : 열거형은 스위치 문과 함께 사용될 때 유용하다. 열거형을 사용하면 스위치 문에서 다양한 상수 값에 대한 분기를 쉽게 작성할 수 있다.

2) 열거형 특징

  • 열거형은 서로 관련된 상수들의 집합을 정의할 때 사용된다.

  • 열거형의 각 상수는 정수 값으로 지정된다.

3) 열거형 구현

  • 열거형 정의
enum MyEnum
{
    Value1,
    Value2,
    Value3
}
  • 열거형 사용
MyEnum myEnum = MyEnum.Value1;
  • 열거형 상수 값 지정
enum MyEnum
{
    Value1 = 10,
    Value2,
    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;
}

4) 사용 예제

  • 요일 출력
enum DaysOfWeek
{
    Sunday,
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday
}

class Program
{
    static void Main(string[] args)
    {
        DaysOfWeek day = DaysOfWeek.Monday;
        Console.WriteLine("Today is " + day);
    }
}
  • 입력 월 출력하기
// 월 열거형
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);
}

-게임 사용 사례

// 게임 상태
enum GameState
{
    MainMenu,
    Playing,
    Paused,
    GameOver
}

// 방향
enum Direction
{
    Up,
    Down,
    Left,
    Right
}

// 아이템 등급
enum ItemRarity
{
    Common,
    Uncommon,
    Rare,
    Epic
}






📌 예외 처리 및 값형과 참조형

01. 예외 처리

1) 예외란?

  • 예외는 프로그램 실행 중에 발생하는 예기치 않은 상황을 의미한다.

  • 예외는 프로그램의 정상적인 흐름을 방해하고 오류를 야기할 수 있다.

2) 예외 처리의 필요성과 장점

  • 예외 처리는 예외 상황에 대비하여 프로그램을 안정적으로 유지하는 데 도움을 준다.

  • 예외 처리를 통해 오류 상황을 적절히 처리하고, 프로그램의 실행을 계속할 수 있다.

  • 예외 처리는 프로그램의 안정성을 높이고 디버깅을 용이하게 한다.

3) 예외 처리 구현

  • C#에서는 try-catch 블록을 사용하여 예외 처리를 수행한다.

  • try 블록 내에서 예외가 발생할 수 있는 코드를 작성하고, catch 블록에서 예외를 처리한다.

try
{
    // 예외가 발생할 수 있는 코드
}
catch (ExceptionType1 ex)
{
    // ExceptionType1에 해당하는 예외 처리
}
catch (ExceptionType2 ex)
{
    // ExceptionType2에 해당하는 예외 처리
}
finally
{
    // 예외 발생 여부와 상관없이 항상 실행되는 코드
}

4) catch블록의 우선순위

  • catch 블록은 위에서부터 순서대로 실행되며, 예외 타입에 해당하는 첫 번째 catch블록이 실행된다.

  • 예외 타입은 상속 관계에 있는 경우 상위 예외 타입의 catch블록이 먼저 실행된다.

5) 다중 catch블록

  • 여러 개의 catch블록을 사용하여 다향한 예외 타입을 처리할 수 있다.

  • 다중 catch블록을 사용하면 각각의 예외 타입에 따라 다른 예외 처리 코드를 작성할 수 있다.

6) 예외 객체

  • catch 블록에서는 예외 객체를 사용하여 예외에 대한 정보를 액세스할 수 있다.

  • 예외 객체를 사용하여 예외의 타입, 메세지 등을 확인하고 처리할 수 있다.






02. finally 블록

1) finally블록의 역할과 사용법

  • finally블록은 예외 발생 여부와 상관없이 항상 실행되는 코드 블록이다.

  • finally블록은 예외 처리의 마지막 단계로, 예외 발생 시 정리 작업이나 리소스 해제 등의 코드를 포함할 수 있다.

  • finally블록은 try-catch블록 뒤에 작성되며, 생략할 수도 있다.

2) finally블록의 실행 시점

  • 예외가 발생한 경우 : 예외가 발생하면 예외 처리 과정을 거친 후 finally블록이 실행된다.

  • 예외가 발생하지 않은 경우 : 예외가 발생하지 않아도 finally블록은 정상적으로 실행된다.






03. 사용자 정의 예외

1) 사용자 정의 예외 클래스 작성

  • 사용자는 필요에 따라 자신만의 예외 클래스를 작성할 수 있다.

  • 사용자 정의 예외 클래스는 Exception클래스를 상속받아 작성하며, 추가적인 기능이나 정보를 제공할 수 있다.

2) 사용자 정의 예외 처리

  • 사용자 정의 예외가 발생한 경우, try-catch블록에서 해당 예외를 처리할 수 있다.

  • catch블록에서 사용자 정의 예외 타입을 명시하여 예외를 처리하고, 예외에 대한 적절한 처리 로직을 작성할 수 있다.






04. 예외 처리 사용 예제

  • 숫자 나누기 예외 처리
try
{
    int result = 10 / 0;  // ArithmeticException 발생
    Console.WriteLine("결과: " + result);
}
catch (DivideByZeroException ex)
{
    Console.WriteLine("0으로 나눌 수 없습니다.");
}
catch (Exception ex)
{
    Console.WriteLine("예외가 발생했습니다: " + ex.Message);
}
finally
{
    Console.WriteLine("finally 블록이 실행되었습니다.");
}

  • 사용자 정의 예외 처리
public class NegativeNumberException : Exception
{
    public NegativeNumberException(string message) : base(message)
    {
    }
}

try
{
    int number = -10;
    if (number < 0)
    {
        throw new NegativeNumberException("음수는 처리할 수 없습니다.");
    }
}
catch (NegativeNumberException ex)
{
    Console.WriteLine(ex.Message);
}
catch (Exception ex)
{
    Console.WriteLine("예외가 발생했습니다: " + ex.Message);
}
  • 다양한 사용 예제
// 플레이어 이동
try
{
    // 플레이어 이동 코드
    if (IsPlayerCollidingWithWall())
    {
        throw new CollisionException("플레이어가 벽에 충돌했습니다!");
    }
}
catch (CollisionException ex)
{
    // 충돌 예외 처리
    Debug.Log(ex.Message);
    // 예외에 대한 추가 처리
}


// 리소스 로딩
try
{
    // 리소스 로딩 코드
    LoadResource("image.png");
}
catch (ResourceNotFoundException ex)
{
    // 리소스가 없는 경우 예외 처리
    Debug.Log(ex.Message);
    // 예외에 대한 추가 처리
}
catch (ResourceLoadException ex)
{
    // 리소스 로딩 중 오류가 발생한 경우 예외 처리
    Debug.Log(ex.Message);
    // 예외에 대한 추가 처리
}


// 게임 상태 전이
try
{
    // 상태 전이 코드
    if (currentGameState != GameState.Playing)
    {
        throw new InvalidStateException("게임이 실행 중이 아닙니다!");
    }
    // 게임 상태 전이 실행
}
catch (InvalidStateException ex)
{
    // 상태 예외 처리
    Debug.Log(ex.Message);
    // 예외에 대한 추가 처리
}





05. 값형과 참조형

C#에서 변수가 데이터를 저장하는 방식을 나타낸다.

1) 값형(Value Type)

  • 값형은 변수에 값을 직접 저장한다.

  • 변수가 실제 데이터를 보유하고 있으며, 해당 변수를 다른 변수에 할당하거나 전달할 때는 값이 복사된다.

  • 값형 변수의 수정은 해당 변수의 값만 변경하므로 다른 변수에 영향을 주지 않는다.

  • int, float, double, bool등의 기본 데이터 타입들이 값형에 해당한다.

struct MyStruct
{
    public int Value;
}

MyStruct struct1 = new MyStruct();
struct1.Value = 10;

MyStruct struct2 = struct1; // struct2는 struct1의 값 복사

struct2.Value = 20;

Console.WriteLine(struct1.Value); // 출력 결과: 10

2) 참조형(Reference Type)

  • 참조형은 변수가 데이터에 대한 참조(메모리 주소)를 저장한다.

  • 변수가 실제 데이터를 가리키는 참조를 갖고 있으며, 해당 변수를 다른 변수에 할당하거나 전달할 때는 참조가 복사된다.

  • 참조형 변수의 수정은 동일한 데이터를 가리키고 있는 다른 변수에 영향을 줄 수 있다.

  • 클래스, 배열, 인터페이스 등이 참조형에 해당한다.

class MyClass
{
    public int Value;
}

MyClass obj1 = new MyClass();
obj1.Value = 10;

MyClass obj2 = obj1; // obj2는 obj1과 동일한 객체를 참조

obj2.Value = 20;

Console.WriteLine(obj1.Value); // 출력 결과: 20

3) 값형과 참조형의 차이점

  • 값형은 실제 데이터를 변수에 저장하고, 참조형은 데이터에 대한 참조를 변수에 저장한다.
  • 값형은 변수 간의 값 복사가 이루어지고, 참조형은 변수 간의 참조 복사가 이루어진다.
  • 값형은 변수가 독립적으로 데이터를 가지며, 참조형은 변수가 동일한 데이터를 참조한다.





06. 박싱과 언박싱

값형과 참조형 사이의 변환을 의미

1) 박싱(Boxing)

  • 박싱은 값형을 참조형으로 변환하는 과정을 말한다.

  • 값형 변수의 값을 메모리의 힙 영역에 할당된 객체로 래핑합니다.

  • 박싱을 통해 값형이 참조형의 특징을 갖게 되며, 참조형 변수로 다뤄질 수 있다.

  • 박싱된 값형은 참조로 전달되므로 메모리 오버헤드가 발생할 수 있다.

2) 언박싱(Unboxing)

  • 언박싱은 박싱된 객체를 다시 값형으로 변환하는 과정을 말한다.

  • 박싱된 객체에서 값을 추출하여 값형 변수에 할당한다.

  • 언박싱은 명시적으로 타입 캐스팅을 해야 하며, 런타임에서 타입 검사가 이루어진다.

  • 잘못된 형식으로 언박싱하면 런타임 에러가 발생할 수 있다.

3) 박싱과 언박싱의 주요 특징

  • 박싱과 언박싱은 값형과 참조형 사이의 변환 작업이므로 성능에 영향을 미칠 수 있다.
    반복적인 박싱과 언박싱은 성능 저하를 초래할 수 있으므로 주의해야 한다.

  • 박싱된 객체는 힙 영역에 할당되므로 가비지 컬렉션의 대상이 될 수 있다. 따라서 메모리 관리에 유의해야 한다.

  • 박싱된 객체와 원래의 값형은 서로 독립적이므로 값을 수정하더라도 상호간에 영향을 주지 않는다.

4) 사용예제

  • object는 .NET Common Type System(CTS)의 일부이며, 모든 클래스의 직간접적인 상위 클래스이다. 모든 클래스는 object에서 상속되며, object는 모든 형식을 참조할 수 있는 포괄적인 타입이다.

박싱과 언박싱 :

using System;

class Program
{
    static void Main()
    {
        // 값형
        int x = 10;
        int y = x;
        y = 20;
        Console.WriteLine("x: " + x); // 출력 결과: 10
        Console.WriteLine("y: " + y); // 출력 결과: 20

        // 참조형
        int[] arr1 = new int[] { 1, 2, 3 };
        int[] arr2 = arr1;
        arr2[0] = 4;
        Console.WriteLine("arr1[0]: " + arr1[0]); // 출력 결과: 4
        Console.WriteLine("arr2[0]: " + arr2[0]); // 출력 결과: 4

        // 박싱과 언박싱
        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로 언박싱

Tip

  • 예외 처리를 할 때는 가능한 구체적인 예외 클래스를 사용하면 좋다. 이렇게 하면 코드가 더욱 안정적이고 예외 상황에 대한 처리가 더욱 정확해진다.

  • 값형과 참조형, 박싱과 언박싱의 개념을 확실히 이해해야 한다. 이는 C#에서 개발을 할 때 매우 중요한 개념 중 하나이다.







📌 델리게이트, 람다 및 LINQ

01. 델리게이트(Delegate)

1) 델리게이트란?

  • 델리게이트(delegate)는 메서드를 참조하는 타입이다.

  • 다른 프로그래밍 언어에서는 함수 포인터라는 용어를 사용하기도 한다.

  • 델리게이트를 이용하면 메서드를 매개변수로 전달하거나 변수에 할당할 수 있다.

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

3) 사용 예제

공격 콜백 받기 :

  • 다음 예제에서는 event를 붙여서 사용했다.
  • event는 할당연산자(=)를 사용할 수 없으며, 클래스 외부에서는 직접 이벤트를 호출할 수 없다.
 // 델리게이트 선언
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);
}





02. 람다(Lambda)

1) 람다란?

  • 람다(lambda)는 익명 메서드를 만드는 방법이다.

  • 람다를 사용하면 메서드의 이름 없이 메서드를 만들 수 있다.

  • 람다는 델리게이트를 사용하여 변수에 할당하거나, 메서드의 매개변수로 전달할 수 있다.

2) 람다 구현

  • 형식
(parameter_list) => expression
  • 정의하기
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();
}





03. Func과 Action

1) Func, Action

  • FuncAction은 델리게이트를 대체하는 미리 정의된 제네릭 형식이다.

  • Func는 값을 반환하는 메서드를 나타내는 델리게이트이다. 마지막 제네릭 형식 매개변수는 반환 타입을 나타낸다. 예를 들어, Funce<int, string>int를 입력으로 받아 string을 반환하는 메서드를 나타낸다.

  • Action은 값을 반환하지 않는 메서드를 나타내는 델리게이트이다. Action은 매개변수를 받아들이지만, 반환 타입이 없다. 예를 들어, Action<int, string>intstring을 입력으로 받고, 아무런 값을 반환하지 않는 메서드를 나타낸다.

  • FuncAction은 제네릭 형식으로 미리 정의되어 있어 매개변수와 반환 타입을 간결하게 표현할 수 있다.

2) 사용 예제

  • 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 예제
// Action을 사용하여 문자열을 출력하는 메서드
void PrintMessage(string message)
{
    Console.WriteLine(message);
}

// Action을 이용한 메서드 호출
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);
        }
    }

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

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

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





04. LINQ(Language Integrated Query)

1) LINQ란?

  • .NET플레임워크에서 제공되는 쿼리 언어 확장

  • 데이터 소스(예 : 컬렉션, 데이터베이스, XML문서 등)에서 데이터를 쿼리하고 조작하는데 사용된다.

  • 데이터베이스 쿼리와 유사한 방식으로 데이터를 필터링, 정렬, 그룹화, 조인 등 다양한 작업을 수행할 수 있다.

  • LINQ는 객체, 데이터베이스, XML문서 등 다양한 데이터 소스를 지원한다.

2) 구조

var result = from 변수 in 데이터소스
             [where 조건식]
             [orderby 정렬식 [, 정렬식...]]
             [select];
  • var키워드는 결과 값의 자료형을 자동으로 추론한다.

  • from절에서는 데이터 소스를 지정한다.

  • where절은 선택적으로 사용하며, 조건식을 지정하여 데이터를 필터링한다.

  • orderby절은 선택적으로 사용하며, 정렬 방식을 지정한다.

  • select절은 선택적으로 사용하며, 조회할 데이터를 지정한다.

3) 간단한 예제

// 데이터 소스 정의 (컬렉션)
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);
}






📌 고급 자요형 및 기능

01. Nullable형

1) null

  • null은 "아무것도 없음"을 의미한다.

  • 참조형 변수가 어떠한 객체를 참조하지 않을 때 사용된다.

2) Nullable

  • Nullable은 C#에서 null값을 가질수 있는 값형에 대한 특별한 형식이다.

  • 기본적으로 값형은 null을 허용하지 않는다.

  • 값형 변수에 null값을 지정할 수 있는 방법을 제공하여 값형이나 구조체를 사용하는 프로그램에서 null상태를 나타낼 수 있다. 주로 값형 변수가 null인지 아닌지를 확인하고 처리해야 할 때 유용하다.

  • 형식은 ?연산자를 사용하여 선언 된다. 예를 들어, int?는 int형식에 null을 할당할 수 있는 Nullable<int>형식을 나타낸다.

3) 사용예제

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





02. 문자열 빌더(StringBuilder)

1) StringBuilder란?

문자열 조작 :
StringBuilder는 Append(), Insert(), Replace(), Remove()등 다양한 메서드를 제공하여 문자열에 대한 추가, 삽입, 치환, 삭제 작업을 수행할 수 있다.

가변성 :
StringBuilder는 내부 퍼버를 사용하여 조작을 수행하므로 크기를 동적으로 조정할 수 있다. 따라서 문자열의 크기가 늘어나거나 줄어들어도 추가적인 메모리 할당이 발생하지 않는다.

효율적인 메모리 관리 :
문자열 조작시 StringBuilder는 내부 버퍼를 사용하여 문자열을 조작하므로, 반복적인 문자열 조작 작업이 발생해도 메모리 할당 및 해제 오버헤드가 크게 감소한다.

2) 주요 메서드

  • Append : 문자열을 뒤에 추가한다.

  • Insert : 문자열을 지정한 위치에 삽입한다.

  • Remove : 지정한 위치에서 문자열을 제거한다.

  • Replace : 문자열의 일부를 다른 문자열로 대체한다.

  • Clear : StringBuilder의 내용을 모두 지운다.

3) 사용예제

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);
profile
No Easy Day

0개의 댓글