[C#] 제네릭 인자 T의 널처리 방식

널일 수 있는 유형을 제네릭 인자로 표현하게 되면 T?이 될 것 같은데요. T가 참조 유형이면 그 동작이 예상이 되지만 만약 T가 값 유형일 경우 어떻게 동작할까요?

먼저 다음 처럼은 쓸 수는 없습니다.

interface IAddable<T?>   // CS1003: 구문 오류
{
    T Add(T a, T b);
}

다음처럼 사용할 수 있습니다.

interface IAddable<T>
{
    T? Add(T? a, T? b);
}

이제 IAddable<T>를 구현해서 값과 참조 유형이 어떻게 처리되는지를 살펴봅시다.

class TestClass : IAddable<int>, IAddable<int?>, IAddable<string>
{
    public int Add(int a, int b)
    {
        return a + b;
    }

    public int? Add(int? a, int? b)
    {
        if (a is null || b is null)
            return null;

        return a + b;
    }

    public string? Add(string? a, string? b)
    {
        if (a is null || b is null)
            return null;

        return a + b;
    }
}

흥미롭게도(?) int 유형의 경우 T? Add(T? a, T? b)임에도 불구하고 int Add(int a, int b)가 되었습니다.

결국에 'T?'의 동작은 [AllowNull] T의 동작과 동일하며, 제네릭 인자 T가 널인지의 확인은 참조형에만 적용된다는 것을 알 수 있습니다.

  • int와 int?는 int와 Nullable<int>로 해석하므로 다른 유형
  • string과 string?은 string과 [AllowNull] string으로 동일한 유형. 단, 컴파일시 특성을 이용해 경고 처리

| 테스트 코드


using System.Runtime.CompilerServices;

var i = new TestClass();

Print(i.Add(5, 4));
Print(i.Add(5, null));
Print(i.Add("A", "B"));

void Print(object? value, [CallerArgumentExpression("value")] string? argumentExpression = null)
{
    System.Console.WriteLine($"{argumentExpression}: {value}");
}

interface IAddable<T>
{
    T? Add(T? a, T? b);
}

class TestClass : IAddable<int>, IAddable<int?>, IAddable<string>
{
    public int Add(int a, int b)
    {
        return a + b;
    }

    public int? Add(int? a, int? b)
    {
        if (a is null || b is null)
            return null;

        return a + b;
    }

    public string? Add(string? a, string? b)
    {
        if (a is null || b is null)
            return null;

        return a + b;
    }
}

//interface IAddable2<T>
//{
//    T Add(T a, T b);
//}

//class TestClass2 : IAddable2<string>, IAddable2<string?> // CS8645: 인터페이스 중복
//{
//    public string Add(string a, string b) // CS8767: 구현된 멤버와 일치하지 않음
//    {
//        throw new NotImplementedException();
//    }

//    public string? Add(string? a, string? b) // CS0111: 동일한 'Add' 멤버
//    {
//        throw new NotImplementedException();
//    }
//}

| 출력

i.Add(5, 4): 9
i.Add(5, null):
i.Add("A", "B"): AB

참고로 제네릭 인자 유형의 제약 조건은 아래를 참조하세요.

6개의 좋아요