사용자 정의 명시적·암시적 변환 연산자 — 다른 타입으로의 변환 제공하기

사용자 정의 명시적·암시적 변환 연산자 — 다른 타입으로의 변환 제공하기

사용자 정의 타입끼리, 또는 내장 타입과의 사이에 표준 변환이 없다면 우리가 직접 implicit(암시적) 또는 explicit(명시적) 변환을 정의할 수 있어요. 이번 글에서는 그 두 키워드로 변환 연산자를 만드는 방법을 설명해 볼게요.

출처: Microsoft Learn

본문

C#을 쓰다 보면 어떤 타입을 다른 타입으로 바꿔야 할 때가 많죠. 그런데 표준 변환이 없는 두 타입 사이에서는 우리가 직접 변환 규칙을 만들어줄 수 있어요. 사용자 정의 타입이 다른 타입으로, 또는 다른 타입에서 자기 타입으로 오는 implicitexplicit 변환을 직접 정의할 수 있는 거예요.

먼저 두 변환의 차이부터 정리해 볼게요. 암시적 변환(implicit conversion) 은 특별한 문법 없이도 저절로 일어나요. 대입 연산이나 메서드 호출 같은 여러 상황에서 자동으로 적용되죠. C#에 기본 내장된 암시적 변환은 항상 성공하고 예외를 던지지 않아요. 그래서 우리가 직접 만드는 암시적 변환도 그렇게 동작해야 해요. 만약 만들려는 변환이 예외를 던질 수 있거나 값을 잃을 수 있는 상황이라면, 그건 명시적 변환으로 정의하는 게 맞아요.

여기서 한 가지 짚고 넘어갈게요. is 연산자와 as 연산자는 사용자 정의 변환을 고려하지 않아요. 다시 말해 이 연산자들로는 우리가 만든 변환 규칙이 적용되지 않는다는 뜻이에요. 사용자 정의 명시적 변환을 호출하려면 캐스트 식을 사용해야 합니다.

변환을 정의할 때는 operator 키워드와 함께 implicit 또는 explicit 키워드를 써요. 그리고 변환을 정의하는 타입은 반드시 그 변환의 원본 타입(source type)이나 대상 타입(target type) 중 하나여야 해요. 두 사용자 정의 타입 사이의 변환은 두 타입 중 어느 쪽에서 정의해도 됩니다.

이제 실제 예시를 볼까요. 아래 코드는 Digit이라는 구조체를 만들고, byte로 암시적 변환, byte에서 Digit으로 명시적 변환을 각각 정의한 모습이에요.

using System;

public readonly struct Digit
{
    private readonly byte digit;

    public Digit(byte digit)
    {
        if (digit > 9)
        {
            throw new ArgumentOutOfRangeException(nameof(digit), "Digit cannot be greater than nine.");
        }
        this.digit = digit;
    }

    public static implicit operator byte(Digit d) => d.digit;
    public static explicit operator Digit(byte b) => new Digit(b);

    public override string ToString() => $"{digit}";
}

public static class UserDefinedConversions
{
    public static void Main()
    {
        var d = new Digit(7);

        byte number = d;
        Console.WriteLine(number);  // output: 7

        Digit digit = (Digit)number;
        Console.WriteLine(digit);  // output: 7
    }
}

코드 흐름을 따라가 볼게요. byte number = d;에서는 Digitbyte로 바꾸는데, 이건 implicit 연산자라서 캐스트 없이 바로 대입돼요. 반면 Digit digit = (Digit)number;에서는 (Digit) 캐스트를 명시적으로 붙여서 byteDigit으로 되돌리고 있어요. Digit 생성자가 9보다 큰 값을 거부하도록 되어 있어서, 만약 범위를 벗어나는 값이 들어오면 예외를 던지게 되죠. 그래서 이 방향의 변환은 값을 잃을 위험이 있어 명시적으로 만들어 둔 거예요.

또 하나 알아둘 점이 있어요. checked 명시적 변환 연산자도 정의할 수 있습니다. 자세한 내용은 Arithmetic operators 문서의 User-defined checked operators 섹션을 참고하세요.

그리고 operator 키워드는 단순히 변환뿐 아니라, C#에 이미 정의되어 있는 연산자를 재정의(오버로딩)할 때도 써요. 이 내용은 Operator overloading 문서에서 다룹니다.

C# 언어 사양

더 자세한 내용은 C# 언어 사양의 다음 섹션들을 참고하세요:

더 알아보기