공부/C#

클래스, 클래스 구성요소, 필드, 접근 지정자, , 상수, 읽기 전용 필드 차이점, 메서드, 식 본문 메서드, 로컬 메서드, 생성자, 분해자, 객체 이니셜라이저, 선택적 매개변수, 프로퍼티, 읽기 전용 프로퍼티, 자동 구현 프로퍼티, 식 본문 프로퍼티, 인덱서, 정적 생성자, 정적 클래스, partial 클래스, nameof 연산자

월러비 2025. 8. 11. 17:19

클래스

클래스의 기본 개념

  • 클래스(Class)는 C#에서 가장 기본적인 참조 타입
  • 클래스는 데이터와 해당 데이터를 처리하는 기능을 하나의 단위로 캡슐화함
  • 현실 세계의 객체나 개념을 프로그램으로 표현할 때 사용함
  • C++에서처럼 :: 연산자를 안쓰고 멤버 접근 연산자만으로 접근이 가능하다.

가장 간단한 클래스 선언

class YourClassName
{

}
  • 위 코드는 가장 단순한 형태의 클래스 선언임
  • 실제로는 이보다 더 많은 멤버들을 포함하여 유용한 기능을 구현함
  • internal 접근지정자 : 프로젝트간에서 외부 내부 프로젝트로 나눠서 접근권한을 주는 것이다.
    • public이긴 하지만 자신의 ‘어셈블리’ 안에서만 접근이 가능하도록 한 접근 지정자다.

클래스의 구성요소

  • 클래스는 다음과 같은 멤버들을 포함할 수 있음:
    • 필드(Field): 데이터를 저장하는 변수
    • 메서드(Method): 기능을 수행하는 코드 블록
    • 프로퍼티(Property): 필드에 접근하는 방법 정의
    • 생성자(Constructor): 객체 초기화 담당
    • 이벤트(Event): 객체의 상태 변화를 알림
    • 인덱서(Indexer): 배열과 같은 인덱스 접근 제공
    • 연산자(Operator): 연산자 동작 정의
      • 연산자 오버로딩을 의미한다.
    • 중첩 타입(Nested Type): 클래스 내부에 정의된 타입
      • 클래스 안에 클래스를 정의한것을 의미한다.
  • 예를 들어 게임의 캐릭터를 표현하는 클래스는 다음과 같이 구성할 수 있음:
class Character
{
    // 필드 - 캐릭터의 상태 저장
    string name;
    int health;
    int level;

    // 메서드 - 캐릭터의 행동 정의
    void Attack() { }
    void TakeDamage(int damage) { }
    void LevelUp() { }
}

필드(Fields)

  • 필드는 클래스나 구조체의 멤버 변수임
  • 객체의 상태를 저장하는 데 사용됨
  • 게임 캐릭터의 체력이나 은행 계좌의 잔액과 같은 데이터를 표현할 때 사용함
  • 멤버 하나하나마다 접근권한을 줘야한다.
    • 기본값은 private다.
  • C#은 지역변수를 제외하고 기본값이 정해져있어서 쓰레기값이 들어가지 않는다.
class Octopus
{
    string name;        // private 필드 - 클래스 내부에서만 접근 가능
    public int Age = 10;  // public 필드 - 어디서든 접근 가능
}

필드 수식자(Field Modifiers)

  • 다음과 같은 수식자 사용 가능:
    • static: 정적 필드. 클래스의 모든 인스턴스가 공유하는 값
    • public, internal, private, protected: 접근 수식자로 필드의 접근 범위 지정
    • readonly: 읽기 전용 필드. 생성자에서만 값 할당 가능
      • const와의 차이점 : const는 컴파일 타임때 정해진다.
        • 런타임때 할당할 수 없다.
      • readonly는 런타임때 할당되고 그 다음부터 변경이 안된다.
    • new: 상속받은 멤버를 숨기는 데 사용
      • 필드 수식자와 키워드의 기능이 다르다.
      • 키워드 new : 객체 동적 생성
class BankAccount
{
    // 모든 계좌에서 공유하는 정적 필드
    static decimal interestRate;

    // 각 계좌마다 고유한 인스턴스 필드
    readonly string accountNumber;
    decimal balance;

    public BankAccount(string number)
    {
        accountNumber = number;  // readonly 필드는 생성자에서 초기화
    }
}

필드 초기화

  • 필드는 선언과 동시에 초기화할 수 있음
  • 초기화하지 않은 필드는 해당 타입의 기본값을 가짐(숫자는 0, 참조 타입은 null)
  • 필드 초기화는 생성자보다 먼저 실행됨
class Example
{
    // 직접 초기화
    public int Count = 10;

    // 메서드 호출 결과로 초기화
    static readonly string TempFolder = System.IO.Path.GetTempPath();

    // 여러 필드 한번에 선언과 초기화
    static readonly int legs = 8,
                        eyes = 2;
}

상수(Constants)

  • 상수는 컴파일 시점에 값이 결정되는 불변값
  • const 키워드로 선언함
  • bool, char, string, 숫자 타입, enum 타입만 가능함
  • 프로그램에서 변경되지 않는 값을 표현할 때 사용함(예: 원주율, 최대값 등)
  • 상수는 인스턴스처럼 쓸 수 있다
public class Test
{
    // 프로그램 전체에서 사용할 상수값 정의
    public const string Message = "Hello World";
    public const int MaxRetries = 3;
    public const double PI = 3.141592653589793;
}

상수 vs 읽기 전용 필드

  • 상수(const)와 읽기 전용 필드(readonly)는 비슷해 보이지만 중요한 차이가 있음:
  1. 값이 결정되는 시점
    • 상수: 컴파일 시점에 값이 결정됨
    • readonly: 실행 시점(런타임)에 값이 결정될 수 있음
  2. 사용해야 할 경우
    • 상수: 절대 변하지 않는 값(예: 원주율)
    • readonly: 객체마다 다른 값을 가질 수 있는 경우(예: 생성 시간)
public class Example
{
    // 컴파일 시점에 값 결정
    public const decimal Version = 2.3M;

    // 실행 시점에 값 결정
    public static readonly DateTime StartupTime = DateTime.Now;
}

지역 상수

  • 메서드 내부에서도 상수를 선언할 수 있음
void Test() 
{
    const double twoPI = 2 * System.Math.PI;
}

메서드(Method)

  • 메서드는 입력 데이터(매개변수)를 받아 처리하고 결과를 반환하는 코드 블록임.
  • 메서드 원형(Signature)은 이름과 매개변수 타입의 순서로 구성됨.

메서드에 사용 가능한 모디파이어

분류 모디파이어 설명

정적 static 인스턴스 없이 클래스에서 직접 호출 가능
접근성 public, internal, private, protected 메서드의 접근 범위 지정
상속 new, virtual, abstract, override, sealed 상속 관련 동작 지정
부분 메서드 partial 부분 클래스의 메서드 정의

식 본문 메서드(Expression-bodied Method)

  • 단일 식으로 구성된 메서드는 람다 연산자(=>)를 사용하여 간단히 표현할 수 있음:
  • 메서드의 ‘본문’을 ‘식’으로 쓰는 것이다.
  • 단순하게 return문만 있는 경우에 이렇게 한줄로 쓸 수 있다.
// 일반적인 메서드
int Foo(int x) { return x * 2; } //문 형태

// 식 본문 메서드
int Foo(int x) => x * 2; //식 형태

로컬 메서드(Local Method)

  • 메서드 내부에 정의된 메서드임.
  • 바깥 메서드의 변수와 매개변수에 접근 가능함.
    • 즉, 로컬 메서드가 선언된 메서드에서 선언된 변수와 매개변수에 접근 가능해진다.
  • 오버로딩이 불가능함.
  • static 키워드를 사용하면 바깥 메서드의 변수 접근을 막을 수 있음. / Instance 멤버에 접근 못함
  • 로컬 메서드가 선언된 메서드 안에서만 접근할 수 있다.
void WriteCubes()
{
    int baseValue = 10;
    Console.WriteLine(Cube(3));      // 27

    // 로컬 메서드 - baseValue 접근 가능
    int Cube(int value) => value * value * value + baseValue;

    // static 로컬 메서드 - baseValue 접근 불가
    static int StaticCube(int value) => value * value * value;
}

최상위문에서의 메서드

  • 최상위문에서 선언된 메서드는 로컬 메서드로 취급됨:
// 최상위문
int x = 3;
PrintValue();        // 3 출력

// 최상위문의 메서드 - x 변수 접근 가능
void PrintValue() => Console.WriteLine(x);

// 오버로딩 시도 - 컴파일 오류
void PrintValue(int y) => Console.WriteLine(y);  // 오류!

인스턴스 생성자(Instance Constructor)

  • 생성자는 객체 초기화를 담당하는 특별한 메서드임.
  • 생성자를 작성하지 않아도 자동으로 기본 생성자가 생성된다.

암시적 매개변수 없는 생성자 (기본생성자)

  • 생성자를 정의하지 않으면 컴파일러가 자동으로 public 매개변수 없는 생성자를 생성함.
  • 하나라도 생성자를 정의하면 자동 생성되지 않음:
public class Player
{
    // 자동 생성된 생성자 사용 가능
}

var p = new Player();  // OK

public class Player2
{
    public Player2(string name) { }  // 생성자 정의
}

var p2 = new Player2();  // 컴파일 오류!

생성자와 필드 초기화 순서

  • 필드 초기화는 생성자 실행 전에 선언 순서대로 수행됨:
public class Player
{
    int shields = 50;   // 첫 번째로 초기화
    int health = 100;   // 두 번째로 초기화

    public Player()
    {
        // 필드 초기화 후 생성자 실행
        Console.WriteLine($"Health: {health}, Shields: {shields}");
        shields = 500;
        health = 200;
        Console.WriteLine($"Health: {health}, Shields: {shields}");
    }
}

비공개 생성자

  • private 생성자로 인스턴스 생성을 제어할 수 있음:
public class Class1
{
    private Class1() { }  // 비공개 생성자

    // 정적 팩토리 메서드로 인스턴스 생성
    public static Class1 Create()
    {
        // 특별한 로직 수행 후 인스턴스 반환
        return new Class1();
    }
}

// 사용 예
var obj = Class1.Create();  // OK
var obj2 = new Class1();    // 컴파일 오류!

분해자 (Deconstructor)

  • 객체의 상태를 여러 변수로 분해할 수 있음.
  • 변수 타입을 생략하거나 특정 값을 무시할 수 있음:
  • 클래스와 객체가 있을때 객체의 멤버의 값을 가져오고싶을때 사용한다.
public class Rectangle
{
    public readonly float Width, Height;

    public Rectangle(float width, float height)
    {
        Width = width;
        Height = height;
    }

    public void Deconstruct(out float width, out float height)
    {
        width = Width;
        height = Height;
    }
}

// 다양한 해체 방법
var rect = new Rectangle(3, 4);

// 명시적 타입
float width, height;
rect.Deconstruct(out width, out height);

// var 사용
var (w, h) = rect;

// 특정 값 무시
var (_, h2) = rect;

// 기존 변수 사용
float existingWidth = 0, existingHeight = 0;
(existingWidth, existingHeight) = rect;

객체 이니셜라이저(Object Initializer)

객체 이니셜라이저 vs 선택적 매개변수

  • 두 가지 방식으로 객체를 초기화할 수 있음:
// 방식 1: 객체 이니셜라이저
public class Bunny
{
    public string Name;
    public bool LikesCarrots;
    public bool LikesHumans;
}

var b1 = new Bunny
{
    Name = "Bo",
    LikesCarrots = true
};

// 방식 2: 선택적 매개변수
public class Bunny2
{
    public string Name { get; }
    public bool LikesCarrots { get; }
    public bool LikesHumans { get; }

    public Bunny2(
        string name,
        bool likesCarrots = false,
        bool likesHumans = false)
    {
        Name = name;
        LikesCarrots = likesCarrots;
        LikesHumans = likesHumans;
    }
}

var b2 = new Bunny2("Bo", likesCarrots: true);

  • 선택적 매개변수의 장점:
    • 읽기 전용 속성을 사용할 수 있음
    • 생성자에서 매개변수 유효성 검사 가능
  • 선택적 매개변수의 단점:
    • 이진 호환성 문제가 있음 (새 매개변수 추가 시 다시 컴파일 필요)
    • 비파괴적 변경(non-destructive mutation)이 어려움
  • 정리
    • 선택적 매개변수 + 읽기 전용 프로퍼티(get) : 불변 객체를 만들고 싶을 때
    • 객체 이니셜라이저 : 값을 유연하게 바꾸고 싶거나, JSON / 데이터 매핑처럼 여러 값을 한꺼번에 세팅할 떄

this 참조

  • this 키워드는 현재 인스턴스를 참조함.
  • 주로 필드와 매개변수의 이름이 같을 때 구분하는데 사용됨:
public class Test
{
    string name;
    public Test(string name)
    {
        this.name = name;  // this로 필드 구분
    }
}

  • 인스턴스 메서드에서 다른 메서드를 호출할 때도 사용 가능:
public class Panda
{
    public Panda Mate;
    
    public void Marry(Panda partner)
    {
        Mate = partner;
        partner.Mate = this;  // 현재 인스턴스를 파트너의 메이트로 지정
    }
}

프로퍼티(Properties)

  • C#의 멤버 요소 중 하나다.

프로퍼티의 개념과 필드와의 차이점

  • 프로퍼티는 필드처럼 보이지만 내부적으로는 메서드와 같이 동작하는 멤버임
    • C++의 getter와 setter의 역할을 수행한다.
  • 프로퍼티를 통해 필드에 대한 접근을 제어하고 유효성 검사 등의 로직을 추가할 수 있음
  • 프로퍼티는 캡슐화(encapsulation)를 구현하는 주요 방법임

프로퍼티의 기본 구문

  • get/set 접근자(accessor)를 사용하여 값을 읽고 쓰는 동작을 정의함
  • get 접근자는 프로퍼티의 값을 반환함
  • set 접근자는 value라는 암시적 매개변수를 통해 새 값을 받음
  • value : set구문 안에서 키워드로 쓰인다.
    • 프로퍼티의 데이터형을 가진다.
    • 외부에서 프로퍼티에 할당한 값을 복사해서 가져온다.
    • 약속되어있는 문법이기에 value 이외의 것을 작성하지 못한다.
  • 메모리는 필드와 멤버 변수만 만들어지기에 프로퍼티의 메모리는 만들어지지 않는다.
using System;

// 프로퍼티 사용 예제
var msft = new Stock();
msft.CurrentPrice = 30;        // set 접근자 호출
msft.CurrentPrice -= 3;        // get과 set 접근자 호출
Console.WriteLine(msft.CurrentPrice); // 출력: 27

// Stock 클래스 정의
public class Stock
{
    decimal currentPrice;   // 비공개 "백킹" 필드

    public decimal CurrentPrice  // 공개 프로퍼티
    {
        get { return currentPrice; } //읽을 때 사용
        set { currentPrice = value; } //값을 할당할때 사용
    }
}

읽기 전용 및 계산된 프로퍼티

  • get 접근자만 정의하면 읽기 전용 프로퍼티가 됨
  • 다른 데이터로부터 값을 계산할 수 있음
using System;

// 계산된 프로퍼티 사용 예제
var stock = new Stock { CurrentPrice = 30, SharesOwned = 100 };
Console.WriteLine(stock.Worth);  // 출력: 3000

public class Stock
{
    public decimal CurrentPrice { get; set; }
    public decimal SharesOwned { get; set; }

    // 계산된 읽기 전용 프로퍼티
    public decimal Worth
    {
        get { return CurrentPrice * SharesOwned; }
    }
}

  • public decimal CurrentPrice { get; set; } → 단순하게 get과 set을 한다면 이렇게 간단하게 선언할 수 있다.
    • 다만, 이렇게하면 단순한 할당 말고 다른 작업을 할 수 없다.
  • 보통 public으로 선언하지않고 auto property로 쓰다가 확장이 필요할때 이렇게 사용한다.

자동 구현 프로퍼티

  • 간단한 프로퍼티는 자동 구현 프로퍼티(auto-implemented property) 구문을 사용할 수 있음
  • 컴파일러가 자동으로 비공개 백킹 필드를 생성함
    • 안보이는 필드가 선언되어있다고 생각해라
  • 자동 프로퍼티는 메모리가 생성된다.
    • 이유 : Symbol에 값을 할당했기 때문이다.
using System;

// 자동 구현 프로퍼티 사용 예제
var stock = new Stock();
stock.Symbol = "MSFT";
Console.WriteLine(stock.Symbol); // 출력: MSFT

public class Stock
{
    // 자동 구현 프로퍼티
    public string Symbol { get; set; }
    public decimal CurrentPrice { get; set; }
}

프로퍼티 초기화

  • 자동 구현 프로퍼티는 필드처럼 초기화할 수 있음
using System;

var stock = new Stock();
Console.WriteLine(stock.MaxPrice); // 출력: 999

public class Stock
{
    // 초기값을 가진 자동 구현 프로퍼티
    public decimal MaxPrice { get; set; } = 999;
}

get/set 접근자의 접근성

  • get과 set 접근자는 서로 다른 접근 수준을 가질 수 있음
  • 일반적으로 set 접근자의 접근성을 더 제한하는 데 사용됨
using System;

var foo = new Foo();
Console.WriteLine(foo.X);  // 읽기 가능
// foo.X = 100;           // 컴파일 오류: set은 private임

public class Foo
{
    private decimal x;
    public decimal X
    {
        get { return x; } //프로퍼티 선언을 따라간다 (public)
        private set { x = Math.Round(value, 2); }
    }
}

  • get set 접근권한을 쓰지 않으면 프로퍼티 선언을 따라간다.
    • 지금은 public

init 접근자 - 유니티 불가능

  • C# 9.0부터 도입된 init 접근자는 객체 초기화 시에만 값을 설정할 수 있게 함
  • 이후에는 값을 변경할 수 없어 불변성(immutability)을 보장함
using System;

// init 접근자 사용 예제
var note = new Note { Pitch = 50 };
Console.WriteLine(note.Pitch); // 출력: 50
// note.Pitch = 51;          // 컴파일 오류: init-only 프로퍼티는 초기화 후 변경 불가

public class Note
{
    public int Pitch { get; init; } = 20;
    public int Duration { get; init; } = 100;
}

식 본문 프로퍼티 (Expression-bodied properties)

  • 람다 식 형태로 프로퍼티를 간단히 정의할 수 있음
  • => 연산자를 사용하여 표현함
  • get만 받는 읽기전용 프로퍼티가 된다.
using System;

var stock = new Stock { CurrentPrice = 30, SharesOwned = 100 };
Console.WriteLine(stock.Worth); // 출력: 3000

public class Stock
{
    public decimal CurrentPrice { get; set; }
    public decimal SharesOwned { get; set; }

    // 식 본문 프로퍼티
    public decimal Worth => CurrentPrice * SharesOwned;
    
    //식 본문 메서드
    //public decimal Worth() => CurrentPrice * SharesOwned;
}

인덱서(Indexers)

  • [] 인덱스 연산자가 재구현된것을 ‘인덱서’라고 부른다.
  • 반환하는 데이터형은 재구현하는 데이터형에 따라 바꿀 수 있다.

인덱서의 개념

  • 인덱서는 클래스나 구조체가 배열처럼 인덱스로 접근할 수 있게 해주는 멤버임
  • C# 문자열은 인덱서를 사용하여 개별 문자에 접근할 수 있음:
using System;

string s = "hello";
Console.WriteLine(s[0]); // 'h' 출력
Console.WriteLine(s[3]); // 'l' 출력

인덱서 구현

  • 인덱서는 this 키워드를 사용하여 정의함
  • 배열의 인덱스처럼 대괄호([])를 사용하여 접근함
  • get과 set 접근자를 포함할 수 있음

다음은 문자열 배열을 포함하는 클래스에서 인덱서를 구현한 예제임:

using System;

// 먼저 객체 생성과 사용
Sentence s = new Sentence();
Console.WriteLine(s[3]);     // "fox" 출력
s[3] = "kangaroo";           // 값 변경
Console.WriteLine(s[3]);     // "kangaroo" 출력

// 그 다음 클래스 정의
class Sentence
{
    string[] words = "The quick brown fox".Split();

    // 인덱서 정의
    public string this[int wordNum]
    {
        get { return words[wordNum]; }
        set { words[wordNum] = value; }
    }
}

다중 매개변수 인덱서

  • 하나의 타입이 여러 인덱서를 가질 수 있음
  • 각 인덱서는 서로 다른 매개변수 타입을 가질 수 있음
  • 하나의 인덱서가 여러 매개변수를 가질 수도 있음
using System;

// 먼저 사용 예제
var obj = new MultiIndexer();
string value = obj[1, "test"];  // 여러 매개변수로 접근

// 그 다음 클래스 정의
class MultiIndexer
{
    // 여러 매개변수를 가진 인덱서
    public string this[int arg1, string arg2]
    {
        get { return $"Called with {arg1} and {arg2}"; }
        set { /* 설정 로직 */ }
    }
}

읽기 전용 인덱서와 식 본문 멤버

  • set 접근자를 생략하면 읽기 전용 인덱서가 됨
  • C#의 식 본문 멤버 문법으로 간단히 표현 가능함
using System;

// 먼저 사용 예제
ReadOnlySentence s = new ReadOnlySentence();
Console.WriteLine(s[0]);  // "The" 출력

// 그 다음 클래스 정의
class ReadOnlySentence
{
    string[] words = "The quick brown fox".Split();

    // 읽기 전용 인덱서를 식 본문으로 정의
    public string this[int wordNum] => words[wordNum]; //get
}

인덱스와 범위(Index and Range) 지원 - 유니티할때 안쓴다.

  • System.Index와 System.Range 타입의 매개변수를 받는 인덱서를 정의할 수 있음
  • ^ 연산자나 .. 연산자로 배열의 끝에서부터 요소에 접근하거나 범위를 지정할 수 있음
using System;

// 먼저 사용 예제
ModernSentence s = new ModernSentence();
Console.WriteLine(s[^1]);     // "fox" 출력 - 마지막 단어
string[] firstTwoWords = s[..2]; // {"The", "quick"} - 처음 두 단어

// 그 다음 클래스 정의
class ModernSentence
{
    string[] words = "The quick brown fox".Split();

    public string this[Index index] => words[index];
    public string[] this[Range range] => words[range];
}

정적 생성자와 정적 클래스

정적 생성자(Static Constructor)

  • 정적 생성자는 인스턴스별이 아닌 타입별로 한 번만 실행됨
  • 타입당 하나의 정적 생성자만 정의할 수 있으며, 매개변수를 가질 수 없음
  • 타입과 동일한 이름을 가져야 함
class Test
{
    static Test() { Console.WriteLine("Test 클래스가 처음 사용됨"); }
}

  • 정적 생성자는 다음 시점에 자동으로 실행됨:
    • 타입의 인스턴스가 생성될 때
    • 타입의 정적 멤버에 접근할 때
  • 정적 생성자는 unsafe와 extern 수정자만 허용됨
  • 처리되지 않은 예외가 발생하면 해당 타입을 더 이상 사용할 수 없게 됨

정적 필드 초기화와 실행 순서

  • 정적 필드 초기화는 정적 생성자가 실행되기 직전에 수행됨
  • 정적 필드는 선언된 순서대로 초기화됨
  • 인스턴스 멤버에서 static 접근 = 가능
  • static 멤버에서 인스턴스 접근 = 불가능
class Foo
{
    public static int X = Y;    // 0
    public static int Y = 3;    // 3
}

// 필드 초기화와 생성자 실행 순서 예제
Console.WriteLine(Foo.X);      // 3

class Foo
{
    public static Foo Instance = new Foo();
    public static int X = 3;

    Foo() { Console.WriteLine(X); }  // 0 출력
}

정적 클래스(Static Class)

  • static으로 표시된 클래스는 인스턴스화나 상속이 불가능함
  • 정적(static) 멤버만 포함할 수 있음
    • C++의 Utils를 생각하면 된다.
    • 생성자도 static이어야한다.
  • System.Console과 System.Math가 대표적인 예시임
static class UtilityClass
{
    public static void PrintMessage()
    {
        Console.WriteLine("유틸리티 메서드");
    }
}

소멸자(Finalizer)

  • 소멸자는 클래스만 가질 수 있는 특수한 메서드임
  • 가비지 컬렉터가 참조되지 않는 객체의 메모리를 회수하기 전에 실행됨
  • 클래스 이름 앞에 ~를 붙여 정의함
  • 가비지 컬렉터가 메모리를 삭제해주기 때문에 정의하는 일이 많이 없다.
class Class1
{
    ~Class1()
    {
        // 리소스 정리 코드
    }
}

  • 실제로는 Object의 Finalize 메서드를 재정의하는 것임:
protected override void Finalize()
{
    try
    {
        // 리소스 정리 코드
    }
    finally
    {
        base.Finalize();
    }
}

  • 단일 문장은 식 본문으로도 작성 가능함:
class Class1
{
    ~Class1() => Console.WriteLine("소멸자 실행");
}

분할 타입과 메서드(Partial Types and Methods)

  • partial 키워드로 타입 정의를 여러 파일로 분할할 수 있음
  • 한 클래스의 코드를 여러 파일에 나누어 작성할 때 사용함
// Monster.cs
partial class Monster
{
    public void Attack() { }
    public void Move() { }
}

// Monster.Animation.cs
partial class Monster
{
    public void PlayAttackAnimation() { }
    public void PlayMoveAnimation() { }
}

  • 분할 메서드는 partial 타입 내에서 메서드의 선언과 구현을 분리할 수 있게 해줌:
partial class Monster
{
    // 선언
    partial void OnDamaged(int damage);
}

partial class Monster
{
    // 구현
    partial void OnDamaged(int damage)
    {
        // 데미지 처리 로직
    }
}

nameof 연산자

nameof 연산자의 개념

  • nameof 연산자는 코드에서 사용된 식별자(identifier)의 이름을 문자열로 반환하는 연산자임
  • 변수, 타입, 메서드, 프로퍼티 등 모든 프로그램 요소의 이름을 가져올 수 있음
  • 정적 타입 검사를 통해 컴파일 시점에 오류를 찾을 수 있음

기본 사용법

  • 가장 단순한 형태는 변수나 타입의 이름을 가져오는 것임:
int count = 123;
string name = nameof(count);    // name은 "count"가 됨
Console.WriteLine(name);        // 출력: count

class MyClass { }
Console.WriteLine(nameof(MyClass));  // 출력: MyClass

타입 멤버 사용

  • 타입의 멤버 이름을 가져올 때는 타입을 포함하여 지정함:
// StringBuilder의 Length 프로퍼티 이름 가져오기
string name = nameof(StringBuilder.Length);  // "Length"

// 전체 경로가 필요한 경우
string fullName = nameof(StringBuilder) + "." +
                 nameof(StringBuilder.Length);  // "StringBuilder.Length"

nameof 연산자의 장점

  • 문자열을 직접 입력하는 것보다 안전함
  • 리팩토링 도구가 심볼 참조를 이해하고 추적할 수 있음
  • 식별자 이름이 변경되면 관련된 모든 참조가 자동으로 수정됨
  • 컴파일 시점에 오타나 잘못된 참조를 검출할 수 있음