partial member

partial member (C# 참조)

partial 멤버는 선언 선언(declaring declaration) 하나와, 보통 구현 선언(implementing declaration) 하나로 이뤄져요. 선언 선언은 본문(body)이 없고, 구현 선언이 멤버의 본문을 제공해요. partial 멤버는 클래스 설계자가 소스 생성기 같은 도구가 구현하게 할 멤버 훅을 제공할 수 있게 해 주는 기능이에요. partial 형식과 멤버는 사람 개발자가 형식의 일부를 쓰고, 도구가 형식의 다른 부분을 쓰게 하는 방식이에요.

개발자가 선택 사항인 구현 선언을 제공하지 않으면, 컴파일러가 컴파일 시점에 선언 선언을 제거할 수 있어요. partial 멤버에는 다음 조건이 적용돼요.

  • 선언은 상황별 키워드 partial로 시작해야 해요.
  • partial 형식의 양쪽 부분에서 시그니처가 일치해야 해요.

partial 키워드는 정적 생성자, 종료자, 오버로드된 연산자에는 쓸 수 없어요. C# 14 이전에는 인스턴스 생성자나 이벤트 선언에도 partial을 쓸 수 없었고, C# 13 이전에는 속성이나 인덱서에도 쓸 수 없었어요.

본문

다음 경우에는 partial 메서드가 구현 선언을 반드시 가질 필요가 없어요.

다음 예제는 위 조건에 맞는 partial 메서드를 보여줘요.

partial class MyPartialClass
{
    // Declaring definition
    partial void OnSomethingHappened(string s);
}

// This part can be in a separate file.
partial class MyPartialClass
{
    // Comment out this method and the program
    // will still compile.
    partial void OnSomethingHappened(string s) =>
        Console.WriteLine($"Something happened: {s}");
}

위 조건 중 하나라도 어긋나는 멤버(예: public virtual partial void 메서드)는 반드시 구현을 제공해야 해요.

partial 속성, 인덱서, 이벤트는 구현 선언에 자동 구현(auto-implemented) 구문을 쓸 수 없어요. 정의 선언은 같은 구문을 사용해요. 구현 선언은 구현된 접근자(accessor)를 하나 이상 포함해야 해요. 그 접근자는 필드 기반 속성(field-backed property)일 수 있어요. partial 이벤트의 구현 선언은 addremove 처리기를 정의해야 해요.

partial 멤버는 소스 생성기와 함께 쓰여도 유용해요. 예를 들어 정규식을 다음 패턴으로 정의할 수 있죠.

public partial class RegExSourceGenerator
{
    [GeneratedRegex("cat|dog", RegexOptions.IgnoreCase, "en-US")]
    private static partial Regex CatOrDogGeneratedRegex();

    private static void EvaluateText(string text)
    {
        if (CatOrDogGeneratedRegex().IsMatch(text))
        {
            // Take action with matching text
        }
    }
}

위 예제는 반드시 구현 선언을 가져야 하는 partial 메서드를 보여줘요. 빌드 과정에서 정규식 소스 생성기가 구현 선언을 만들어 주죠.

다음 예제는 클래스의 선언 선언과 구현 선언을 보여줘요. 메서드의 반환 형식이 void가 아니고(string이죠) 접근이 public이므로, 이 메서드는 반드시 구현 선언을 가져야 해요.

// Declaring declaration
public partial class PartialExamples
{
    /// <summary>
    /// Gets or sets the number of elements that the List can contain.
    /// </summary>
    public partial int Capacity { get; set; }

    /// <summary>
    /// Gets or sets the element at the specified index.
    /// </summary>
    /// <param name="index">The index</param>
    /// <returns>The string stored at that index</returns>
    public partial string this[int index] { get; set; }

    public partial string? TryGetAt(int index);
}

public partial class PartialExamples
{
    private List<string> _items = [
        "one",
        "two",
        "three",
        "four",
        "five"
        ];

    // Implementing declaration

    /// <summary>
    /// Gets or sets the number of elements that the List can contain.
    /// </summary>
    /// <remarks>
    /// If the value is less than the current capacity, the list will shrink to the
    /// new value. If the value is negative, the list isn't modified.
    /// </remarks>
    public partial int Capacity
    {
        get => _items.Count;
        set
        {
            if ((value != _items.Count) && (value >= 0))
            {
                _items.Capacity = value;
            }
        }
    }

    public partial string this[int index]
    {
        get => _items[index];
        set => _items[index] = value;
    }

    /// <summary>
    /// Gets the element at the specified index.
    /// </summary>
    /// <param name="index">The index</param>
    /// <returns>The string stored at that index, or null if out of bounds</returns>
    public partial string? TryGetAt(int index)
    {
        if (index < _items.Count)
        {
            return _items[index];
        }
        return null;
    }
}

위 예제는 두 선언이 어떻게 결합되는지에 대한 규칙을 보여줘요.

  • 시그니처 일치: 일반적으로 선언 선언과 구현 선언의 시그니처는 일치해야 해요. 이 규칙은 메서드, 속성, 인덱서, 개별 접근자의 접근성 한정자를 포함해요. 모든 매개 변수의 매개 변수 형식과 ref 종류 한정자도 포함돼요. 반환 형식과 ref 종류 한정자는 일치해야 하고, 튜플 멤버 이름도 일치해야 해요. 다만 몇몇 규칙은 유연해요.
    • 선언 선언과 구현 선언은 서로 다른 nullable 주석 설정을 가질 수 있어요. 즉 하나는 nullable 무시이고 다른 하나는 nullable 활성화일 수 있죠.
    • 무시(nullable oblivious) null 허용을 다루지 않는 null 허용 차이는 경고를 만들어요.
    • 기본 매개 변수 값은 일치할 필요가 없어요. 메서드나 인덱서의 구현 선언이 기본 매개 변수 값을 선언하면 컴파일러가 경고를 내요.
    • 매개 변수 이름이 일치하지 않으면 컴파일러가 경고를 내요. 내보내진 IL에는 선언 선언의 매개 변수 이름이 담겨요.
  • 문서 주석: 문서 주석은 두 선언 중 어디서든 포함될 수 있어요. 선언 선언과 구현 선언 둘 다 문서 주석을 포함하면 구현 선언의 주석이 포함돼요. 위 예제에서 문서 주석은 이렇게 이뤄져요.
    • Capacity 속성의 주석은 구현 선언에서 가져와요. 두 선언 모두 /// 주석이 있을 때는 구현 선언의 주석이 쓰여요.
    • 인덱서의 주석은 선언 선언에서 가져와요. 구현 선언에는 /// 주석이 없어요.
    • TryGetAt의 주석은 구현 선언에서 가져와요. 선언 선언에는 /// 주석이 없어요.
    • 생성된 XML에는 모든 public 멤버에 대한 문서 주석이 포함돼요.
  • 대부분의 특성 선언은 결합돼요. 다만 모든 호출자 정보 특성(caller info attribute)AllowMultiple=false로 정의돼요. 컴파일러는 선언 선언의 호출자 정보 특성은 인식하지만, 구현 선언의 호출자 정보 특성은 모두 무시해요. 구현 선언에 호출자 정보 특성을 추가하면 컴파일러가 경고를 내요.

더 알아보기