'INumber<TSelf>'를 이용해 숫자로 취급되는 사용자형 만들기

C# 10에 추가될 static abstract와 함께 제네릭 Math가 도입될 예정입니다. 최신 Visual Studio 2022 환경에서 이 기능을 미리보기 할 수 있습니다.

이제, 숫자형은 점진적으로 INumber<TSelf>로 취급되게 되므로 제네릭 Math가 보편적으로 사용되게 되면 완전한 사용자 유형의 숫자형을 만들 수 있게 됩니다.

아래 코드는 미구현된 코드임

record struct Point<TValue>(TValue x, TValue y) : INumber<Point<TValue>> where TValue : INumber<TValue>
{
    public static Point<TValue> One => throw new NotImplementedException();

    public static Point<TValue> Zero => throw new NotImplementedException();

    public static Point<TValue> AdditiveIdentity => throw new NotImplementedException();

    public static Point<TValue> MultiplicativeIdentity => throw new NotImplementedException();

    public static Point<TValue> Abs(Point<TValue> value)
    {
        throw new NotImplementedException();
    }

    public static Point<TValue> Clamp(Point<TValue> value, Point<TValue> min, Point<TValue> max)
    {
        throw new NotImplementedException();
    }

    public static Point<TValue> Create<TOther>(TOther value) where TOther : INumber<TOther>
    {
        throw new NotImplementedException();
    }

    public static Point<TValue> CreateSaturating<TOther>(TOther value) where TOther : INumber<TOther>
    {
        throw new NotImplementedException();
    }

    public static Point<TValue> CreateTruncating<TOther>(TOther value) where TOther : INumber<TOther>
    {
        throw new NotImplementedException();
    }

    public static (Point<TValue> Quotient, Point<TValue> Remainder) DivRem(Point<TValue> left, Point<TValue> right)
    {
        throw new NotImplementedException();
    }

    public static Point<TValue> Max(Point<TValue> x, Point<TValue> y)
    {
        throw new NotImplementedException();
    }

    public static Point<TValue> Min(Point<TValue> x, Point<TValue> y)
    {
        throw new NotImplementedException();
    }

    public static Point<TValue> Parse(string s, NumberStyles style, IFormatProvider? provider)
    {
        throw new NotImplementedException();
    }

    public static Point<TValue> Parse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider? provider)
    {
        throw new NotImplementedException();
    }

    public static Point<TValue> Parse(ReadOnlySpan<char> s, IFormatProvider? provider)
    {
        throw new NotImplementedException();
    }

    public static Point<TValue> Parse(string s, IFormatProvider? provider)
    {
        throw new NotImplementedException();
    }

    public static Point<TValue> Sign(Point<TValue> value)
    {
        throw new NotImplementedException();
    }

    public static bool TryCreate<TOther>(TOther value, out Point<TValue> result) where TOther : INumber<TOther>
    {
        throw new NotImplementedException();
    }

    public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out Point<TValue> result)
    {
        throw new NotImplementedException();
    }

    public static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider? provider, out Point<TValue> result)
    {
        throw new NotImplementedException();
    }

    public static bool TryParse(ReadOnlySpan<char> s, IFormatProvider? provider, out Point<TValue> result)
    {
        throw new NotImplementedException();
    }

    public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, out Point<TValue> result)
    {
        throw new NotImplementedException();
    }

    public int CompareTo(object? obj)
    {
        throw new NotImplementedException();
    }

    public int CompareTo(Point<TValue> other)
    {
        throw new NotImplementedException();
    }

    public string ToString(string? format, IFormatProvider? formatProvider)
    {
        throw new NotImplementedException();
    }

    public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? provider)
    {
        throw new NotImplementedException();
    }

    public static Point<TValue> operator +(Point<TValue> value)
    {
        throw new NotImplementedException();
    }

    public static Point<TValue> operator +(Point<TValue> left, Point<TValue> right)
    {
        throw new NotImplementedException();
    }

    public static Point<TValue> operator -(Point<TValue> value)
    {
        throw new NotImplementedException();
    }

    public static Point<TValue> operator -(Point<TValue> left, Point<TValue> right)
    {
        throw new NotImplementedException();
    }

    public static Point<TValue> operator ++(Point<TValue> value)
    {
        throw new NotImplementedException();
    }

    public static Point<TValue> operator --(Point<TValue> value)
    {
        throw new NotImplementedException();
    }

    public static Point<TValue> operator *(Point<TValue> left, Point<TValue> right)
    {
        throw new NotImplementedException();
    }

    public static Point<TValue> operator /(Point<TValue> left, Point<TValue> right)
    {
        throw new NotImplementedException();
    }

    public static Point<TValue> operator %(Point<TValue> left, Point<TValue> right)
    {
        throw new NotImplementedException();
    }

    public static bool operator <(Point<TValue> left, Point<TValue> right)
    {
        throw new NotImplementedException();
    }

    public static bool operator >(Point<TValue> left, Point<TValue> right)
    {
        throw new NotImplementedException();
    }

    public static bool operator <=(Point<TValue> left, Point<TValue> right)
    {
        throw new NotImplementedException();
    }

    public static bool operator >=(Point<TValue> left, Point<TValue> right)
    {
        throw new NotImplementedException();
    }
}

오 굉장히 놀랍고 흥미롭습니다. 비단 INumber<TSelf>을 사용하지 않더라도 static abstract를 이용해 적절히 인터페이스를 만들고, 이를 대표 키 속성으로 만들 수도 있습니다. 최종적으로 이런 기법이 EF Core에도 적용되리라 생각이 드네요. (Validation 등 사용자 유형에 따라 일괄 처리하게 되어서 좀 더 깔끔한 코드가 예상됩니다.)

좋아요 2

정성태님의 글 참고하면 도움이 됩니다.

.NET Framework: 1090. .NET 6 Preview 7에 추가된 숫자 형식에 대한 제네릭 연산 지원 (sysnet.pe.kr)

.NET Framework: 1091. C# - Python range 함수 구현 (2) INumber를 이용한 개선 (sysnet.pe.kr)

좋아요 2