24.01.18 TIL - [C#] 기초 (12) : 예외처리 및 값형과 참조형

JJwoo·2024년 1월 17일
0

C#

목록 보기
16/20
post-thumbnail

⚓ 예외 처리

  • 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 블록의 실행 시점
    1. 예외가 발생한 경우: 예외가 발생하면 예외 처리 과정을 거친 후 finally 블록이 실행됩니다.
    2. 예외가 발생하지 않은 경우: 예외가 발생하지 않아도 finally 블록은 정상적으로 실행됩니다.

03. 사용자 정의 예외

  • 1) 사용자 정의 예외 클래스 작성
    • 사용자는 필요에 따라 자신만의 예외 클래스를 작성할 수 있습니다.
    • 사용자 정의 예외 클래스는 Exception 클래스를 상속받아 작성하며, 추가적인 기능이나 정보를 제공할 수 있습니다.
  • 2) 사용자 정의 예외 처리
    • 사용자 정의 예외가 발생한 경우, try-catch 블록에서 해당 예외를 처리할 수 있습니다.
    • catch 블록에서 사용자 정의 예외 타입을 명시하여 예외를 처리하고, 예외에 대한 적절한 처리 로직을 작성할 수 있습니다.

04. 게임에서의 예외

  • 예외의 다양한 유형:
    C#에서는 다양한 유형의 예외가 있습니다. 예를 들어, NullReferenceException, IndexOutOfRangeException, IOException 등이 있습니다. 각 예외 유형은 발생하는 상황에 따라 다릅니다. 예를 들어, NullReferenceException은 null 객체를 참조할 때 발생합니다.
  • 유니티에서의 예외 처리:
    유니티 게임 개발에서도 예외 처리는 중요합니다. 예를 들어, 파일을 불러올 때나 네트워크 통신 중에 예외가 발생할 수 있습니다. 이러한 예외를 처리하지 않으면 게임이 갑자기 중단될 수 있습니다. 따라서, 유니티에서도 try-catch 블록을 사용하여 예외를 처리해야 합니다.

  • 예외 처리와 게임 루프:
    게임 개발에서 예외 처리는 게임 루프에 영향을 줄 수 있습니다. 예외가 발생하면 게임의 정상적인 흐름이 방해받을 수 있으므로, 이를 적절히 처리하여 게임이 계속 진행될 수 있도록 해야 합니다.

  • finally 블록의 중요성:
    finally 블록은 예외 발생 여부와 상관없이 실행됩니다. 이를 통해, 예외 발생 시에도 필요한 자원 해제나 정리 작업을 수행할 수 있습니다. 예를 들어, 파일 핸들을 닫거나 네트워크 연결을 종료하는 등의 작업을 finally에서 수행할 수 있습니다.

  • 에러 로깅:
    예외가 발생했을 때, 로그에 기록하는 것도 중요합니다. 이를 통해 개발자는 나중에 오류의 원인을 파악하고 수정할 수 있습니다. 유니티에서는 Debug.Log 또는 Debug.LogError를 사용하여 이러한 로그를 출력할 수 있습니다.

예외 처리는 프로그램의 안정성과 유지보수성을 크게 향상시키는 중요한 기능이며,
유니티 게임 개발에서도 이러한 예외 처리 방법을 적절히 활용하면 더욱 견고하고 안정적인 게임을 만들 수 있습니다.


숫자 나누기 예외 처리

    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);
    // 예외에 대한 추가 처리
}

유니티에서의 예외처리



using System;
using System.IO;
using UnityEngine;

public class GameManager : MonoBehaviour
{
    void Start()
    {
        try
        {
            LoadGameData();
        }
        catch (FileNotFoundException ex)
        {
            Debug.LogError("게임 데이터 파일을 찾을 수 없습니다: " + ex.Message);
            // 여기서 필요한 회복 로직을 수행할 수 있습니다.
        }
        catch (IOException ex)
        {
            Debug.LogError("게임 데이터 파일을 읽는 도중 오류가 발생했습니다: " + ex.Message);
            // 파일 읽기 오류 처리 로직
        }
        catch (Exception ex)
        {
            Debug.LogError("알 수 없는 오류가 발생했습니다: " + ex.Message);
            // 다른 유형의 예외 처리
        }
        finally
        {
            // 파일 읽기 작업 후 항상 실행될 코드, 예를 들면 자원 정리 등
        }
    }

    void LoadGameData()
    {
        // 게임 데이터를 파일에서 읽는 로직
        // 예외가 발생할 수 있는 부분
    }
}

이 예제에서는 LoadGameData 메소드에서 파일을 읽는 과정을 시뮬레이션합니다. 파일이 없거나, 읽기 오류가 발생하는 경우 등 다양한 예외 상황을 try-catch 블록으로 처리합니다.

  • FileNotFoundException은 파일이 없을 때 발생합니다.

  • IOException은 파일을 읽는 도중 오류가 발생했을 때 처리합니다.

  • Exception은 위의 두 예외 이외의 모든 예외를 포착합니다.

  • finally 블록에서는 예외 발생 여부와 관계없이 실행되어야 하는 코드를 넣습니다. 예를 들어, 파일 핸들을 닫거나, 네트워크 연결을 종료하는 등의 작업입니다.

이러한 방식으로 게임에서 예외 처리를 구현함으로써, 예외 상황에서도 게임의 안정성을 유지하고 사용자 경험을 개선할 수 있습니다.


🚡 값형과 참조형

C#에서 데이터에 변수가 저장되는 방식

1) 값형 (Value Type):

  • 값형 변수는 데이터를 스택 메모리에 직접 저장합니다.

  • 변수가 실제 데이터 값을 갖습니다.

  • 기본 데이터 타입들(예: int, float, double, bool, char 등)과 구조체(struct)가 이에 해당합니다.

  • 값형 변수를 다른 변수에 할당할 때, 그 값의 복사본이 생성되며, 원본 변수와 복사본은 서로 독립적입니다.

  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), 배열, 인터페이스, 델리게이트 등이 참조형에 속합니다.

    • 참조형 변수를 다른 변수에 할당하면, 메모리 주소가 복사됩니다. 따라서 두 변수는 동일한 메모리 주소를 참조하게 되고, 같은 객체를 공유하게 됩니다.

    • 이러한 차이는 변수의 할당, 복사, 메모리 관리 등 여러 측면에서 중요한 역할을 합니다.
      예를 들어, 유니티 게임 개발에서는 객체(예: 게임 오브젝트, 컴포넌트 등)를 참조형으로 다루기 때문에, 이러한 차이점을 이해하는 것이 객체의 복사와 참조 관리에 있어 효과적입니다.

    클래스 빼곤 나머지는 값형이라고 생각하면 편하다.

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

값형 vs 참조형 차이 표

특징값형(Value Type)참조형(Reference Type)
저장 위치스택 메모리에 직접 저장힙 메모리에 저장되며, 변수는 메모리 주소를 참조
예시int, float, double, bool, char, 구조체(struct)클래스(class), 배열, 인터페이스, 델리게이트
데이터 할당변수에 값이 직접 저장됨변수는 메모리 주소를 저장하고, 실제 데이터는 힙에 저장됨
복사 시 동작값의 복사본이 생성되며, 원본과 복사본은 독립적임메모리 주소가 복사되며, 복사된 변수들은 같은 객체를 공유함
메모리 관리스택 메모리를 사용하므로 할당과 해제가 빠름힙 메모리를 사용하므로 가비지 컬렉션에 의해 관리됨
사용 시 주의점값이 자주 바뀌거나 크기가 작은 데이터에 적합크기가 크거나 데이터의 공유가 필요할 때 적합
  • 값형과 참조형의 차이점

    • 값형은 실제 데이터를 변수에 저장하고, 참조형은 데이터에 대한 참조를 변수에 저장합니다.

    • 값형은 변수 간의 값 복사가 이루어지고, 참조형은 변수 간의 참조 복사가 이루어집니다.

    • 값형은 변수가 독립적으로 데이터를 가지며, 참조형은 변수가 동일한 데이터를 참조합니다.

🚪 박싱과 언박싱

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

박싱 (Boxing)

  • 박싱은 값형의 인스턴스를 참조형 객체로 변환하는 과정입니다.

  • 이 과정에서 CLR(Common Language Runtime)은 값형 데이터를 힙에 할당하고, 해당 메모리 주소를 참조형 변수에 저장합니다.

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

  • 박싱은 성능 비용이 발생하는 작업이며, 메모리 할당과 복사 과정이 필요합니다.

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

예시:

Copy code
int num = 123;
object obj = num; // 여기서 박싱이 일어납니다.

언박싱 (Unboxing)

  • 언박싱은 박싱된 참조형 객체를 다시 값형 인스턴스로 변환하는 과정입니다.

  • 이 과정에서 참조형 객체의 값을 새로운 값형 변수에 복사합니다.

  • 언박싱도 박싱과 마찬가지로 성능 비용이 발생합니다.

  • 언박싱은 명시적 캐스팅이 필요하며, 캐스팅하는 타입이 올바르지 않으면 InvalidCastException이 발생할 수 있습니다.

object obj = 123; // 박싱
int num = (int)obj; // 여기서 언박싱이 일어납니다.

박싱과 언박싱의 주요 특징

  • 박싱과 언박싱은 값형과 참조형 사이의 변환 작업이므로 성능에 영향을 미칠 수 있습니다.

  • 반복적인 박싱과 언박싱은 성능 저하를 초래할 수 있으므로 주의해야 합니다.

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

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

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로 언박싱
profile
개발 모코코

0개의 댓글