다음을 통해 공유


자습서: 인터페이스에서 정적 가상 멤버 탐색

인터페이스 정적 가상 멤버 를 사용하면 오버로드된 연산 자 또는 기타 정적 멤버를 포함하는 인터페이스를 정의할 수 있습니다. 정적 멤버를 사용하여 인터페이스를 정의한 후에는 이러한 인터페이스를 제약 조건으로 사용하여 연산자 또는 기타 정적 메서드를 사용하는 제네릭 형식을 만들 수 있습니다. 오버로드된 연산자를 사용하여 인터페이스를 만들지 않더라도 이 기능과 언어 업데이트에서 사용하도록 설정된 제네릭 수학 클래스를 활용할 수 있습니다.

이 튜토리얼에서는 다음을 배우게 됩니다:

  • 정적 멤버를 사용하여 인터페이스를 정의합니다.
  • 인터페이스를 사용하여 정의된 연산자를 사용하여 인터페이스를 구현하는 클래스를 정의합니다.
  • 정적 인터페이스 메서드를 사용하는 제네릭 알고리즘을 만듭니다.

필수 조건

정적 추상 인터페이스 메서드

예제로 시작해 보겠습니다. 다음 메서드는 두 double 숫자의 중간점을 반환합니다.

public static double MidPoint(double left, double right) =>
    (left + right) / (2.0);

숫자 형식,intshortlongfloat 또는 숫자를 나타내는 모든 형식decimal에 대해 동일한 논리가 작동합니다. +/ 연산자를 사용하는 방법과 2 값을 정의하는 방법이 필요합니다. 인터페이스를 System.Numerics.INumber<TSelf> 사용하여 이전 메서드를 다음 제네릭 메서드로 작성할 수 있습니다.

public static T MidPoint<T>(T left, T right)
    where T : INumber<T> => (left + right) / T.CreateChecked(2);  // note: the addition of left and right may overflow here; it's just for demonstration purposes

INumber<TSelf> 인터페이스를 구현하는 모든 형식에는 operator +operator /에 대한 정의가 포함되어야 합니다. 분모는 모든 숫자 형식에 대해 2 값을 T.CreateChecked(2)로 정의하여, 분모가 두 매개 변수와 동일한 형식을 갖도록 강제합니다. INumberBase<TSelf>.CreateChecked<TOther>(TOther)는 지정된 값에서 형식의 인스턴스를 생성하고, 값이 표현 가능한 범위를 벗어나면 OverflowException를 발생시킵니다. 이 구현은 leftright가 모두 충분히 큰 값일 경우 오버플로가 발생할 가능성이 있습니다. 이 잠재적인 문제를 방지할 수 있는 대체 알고리즘이 있습니다.

친숙한 구문을 사용하여 인터페이스에서 정적 추상 멤버를 정의합니다. 구현을 제공하지 않는 정적 멤버에 staticabstract 한정자를 추가합니다. 다음 예제에서는 operator ++을(를) 재정의할 수 있는 모든 형식에 적용할 수 있는 IGetNext<T> 인터페이스를 정의합니다.

public interface IGetNext<T> where T : IGetNext<T>
{
    static abstract T operator ++(T other);
}

형식 인수 TIGetNext<T>를 구현한다는 제약 조건은 연산자의 서명에 포함하는 형식이나 해당 형식 인수가 포함되도록 보장합니다. 많은 연산자는 해당 매개 변수가 일치하는 형식이거나, 포함하는 형식을 구현하도록 제한된 형식 매개 변수여야 한다고 강제합니다. 이 제약 조건이 없으면 인터페이스에서 ++ 연산자를 IGetNext<T> 정의할 수 없습니다.

각 증분에서 다음 코드를 사용하여 문자열에 다른 문자를 추가하는 'A' 문자 문자열을 만드는 구조를 만들 수 있습니다.

public struct RepeatSequence : IGetNext<RepeatSequence>
{
    private const char Ch = 'A';
    public string Text = new string(Ch, 1);

    public RepeatSequence() {}

    public static RepeatSequence operator ++(RepeatSequence other)
        => other with { Text = other.Text + Ch };

    public override string ToString() => Text;
}

일반적으로, ++를 "이 형식의 다음 값을 생성"으로 정의하여 사용할 수 있는 모든 알고리즘을 구축할 수 있습니다. 이 인터페이스를 사용하면 명확한 코드와 결과를 얻을 수 있습니다.

var str = new RepeatSequence();

for (int i = 0; i < 10; i++)
    Console.WriteLine(str++);

앞의 예제에서는 다음 출력을 생성합니다.

A
AA
AAA
AAAA
AAAAA
AAAAAA
AAAAAAA
AAAAAAAA
AAAAAAAAA
AAAAAAAAAA

이 작은 예제에서는 이 기능에 대한 동기를 보여 줍니다. 연산자, 상수 값 및 기타 정적 작업에 자연 구문을 사용할 수 있습니다. 오버로드된 연산자를 포함하여 정적 멤버를 사용하는 여러 형식을 만들 때 이러한 기술을 살펴볼 수 있습니다. 형식의 기능과 일치하는 인터페이스를 정의한 다음 새 인터페이스에 대한 해당 형식의 지원을 선언합니다.

제네릭 수학

인터페이스에서 연산자를 포함한 정적 메서드를 허용하는 동기 부여 시나리오는 제네릭 수학 알고리즘을 지원하는 것입니다. .NET 7 기본 클래스 라이브러리에는 많은 산술 연산자의 인터페이스 정의가 있으며, INumber<T> 인터페이스 내에서 많은 산술 연산자를 결합하는 파생 인터페이스가 포함되어 있습니다. 이러한 형식을 적용하여 T가 모든 숫자 형식을 사용할 수 있는 Point<T> 레코드를 작성해 보겠습니다. 일부 XOffsetYOffset 연산자를 사용하여 + 점을 이동할 수 있습니다.

먼저 dotnet new 또는 Visual Studio를 사용하여 새 콘솔 애플리케이션을 만듭니다.

Translation<T>Point<T>의 공용 인터페이스는 다음 코드처럼 보여야 합니다.

// Note: Not complete. This won't compile yet.
public record Translation<T>(T XOffset, T YOffset);

public record Point<T>(T X, T Y)
{
    public static Point<T> operator +(Point<T> left, Translation<T> right);
}

record 형식과 Translation<T>, Point<T> 형식 모두 두 값을 저장하며, 정교한 동작보다는 데이터 스토리지를 나타냅니다. 구현 operator + 은 다음 코드와 같습니다.

public static Point<T> operator +(Point<T> left, Translation<T> right) =>
    left with { X = left.X + right.XOffset, Y = left.Y + right.YOffset };

이전 코드를 컴파일하려면 T이(가) IAdditionOperators<TSelf, TOther, TResult> 인터페이스를 지원한다고 선언해야 합니다. 해당 인터페이스에는 정적 메서드가 operator + 포함됩니다. 세 가지 형식 매개 변수를 선언합니다. 하나는 왼쪽 피연산자, 다른 하나는 오른쪽 피연산자, 다른 하나는 결과에 대한 매개 변수입니다. 일부 형식은 다른 피연산자 및 결과 형식에 대해 구현 + 합니다. 형식 인수 TIAdditionOperators<T, T, T>을(를) 구현한다는 선언을 추가합니다.

public record Point<T>(T X, T Y) where T : IAdditionOperators<T, T, T>

해당 제약 조건을 Point<T> 추가한 후 클래스는 더하기 연산자에 + 사용할 수 있습니다. 선언에 동일한 제약 조건을 추가합니다 Translation<T> .

public record Translation<T>(T XOffset, T YOffset) where T : IAdditionOperators<T, T, T>;

IAdditionOperators<T, T, T> 제약 조건은 개발자가 점 추가에 대한 제약 조건을 충족하지 않는 형식을 사용하여 Translation 클래스를 생성하는 것을 방지합니다. 형식 매개 변수 Translation<T>Point<T> 에 필요한 제약 조건을 추가했기 때문에 이 코드가 작동합니다. Program.cs 파일에서 TranslationPoint 선언 위에 다음과 같은 코드를 추가하여 테스트할 수 있습니다.

var pt = new Point<int>(3, 4);

var translate = new Translation<int>(5, 10);

var final = pt + translate;

Console.WriteLine(pt);
Console.WriteLine(translate);
Console.WriteLine(final);

이러한 형식이 적절한 산술 인터페이스를 구현한다고 선언하여 이 코드를 더 재사용 가능하게 만들 수 있습니다. 첫 번째로 해야 할 변경 사항은 Point<T, T>IAdditionOperators<Point<T>, Translation<T>, Point<T>> 인터페이스를 구현한다는 것을 선언하는 것입니다. 이 형식은 Point 피연산자와 결과에 대해 서로 다른 형식을 사용합니다. 이 형식은 Point 이미 해당 서명을 사용하여 operator + 구현하므로 선언에 인터페이스를 추가하는 것만 있으면 됩니다.

public record Point<T>(T X, T Y) : IAdditionOperators<Point<T>, Translation<T>, Point<T>>
    where T : IAdditionOperators<T, T, T>

마지막으로 추가를 수행할 때 해당 형식에 대한 추가 ID 값을 정의하는 속성을 갖는 것이 유용합니다. 해당 기능에 IAdditiveIdentity<TSelf,TResult>대한 새 인터페이스가 있습니다. 변환 {0, 0} 은 가산 ID입니다. 결과 지점은 왼쪽 피연산자와 동일합니다. 인터페이스는 IAdditiveIdentity<TSelf, TResult> ID 값을 반환하는 하나의 읽기 전용 속성을 AdditiveIdentity정의합니다. 이 Translation<T> 인터페이스를 구현하려면 몇 가지 변경 내용이 필요합니다.

using System.Numerics;

public record Translation<T>(T XOffset, T YOffset) : IAdditiveIdentity<Translation<T>, Translation<T>>
    where T : IAdditionOperators<T, T, T>, IAdditiveIdentity<T, T>
{
    public static Translation<T> AdditiveIdentity =>
        new Translation<T>(XOffset: T.AdditiveIdentity, YOffset: T.AdditiveIdentity);
}

여기에 몇 가지 변경 사항이 있으므로 하나씩 살펴보겠습니다. 먼저 Translation 형식이 IAdditiveIdentity 인터페이스를 구현한다고 선언합니다.

public record Translation<T>(T XOffset, T YOffset) : IAdditiveIdentity<Translation<T>, Translation<T>>

다음 코드와 같이 인터페이스 멤버를 구현하려고 할 수 있습니다.

public static Translation<T> AdditiveIdentity =>
    new Translation<T>(XOffset: 0, YOffset: 0);

위의 코드는 형식에 따라 달라지므로 0 컴파일되지 않습니다. 그 답은 IAdditiveIdentity<T>.AdditiveIdentity을(를) 0에 사용하세요. 이러한 변경은 이제 TIAdditiveIdentity<T>을(를) 구현해야 한다는 제약 조건을 포함해야 함을 의미합니다. 그러면 다음 구현이 수행됩니다.

public static Translation<T> AdditiveIdentity =>
    new Translation<T>(XOffset: T.AdditiveIdentity, YOffset: T.AdditiveIdentity);

이제 해당 제약 조건을 Translation<T>추가했으므로 다음과 같은 제약 조건을 추가해야 합니다 Point<T>.

using System.Numerics;

public record Point<T>(T X, T Y) : IAdditionOperators<Point<T>, Translation<T>, Point<T>>
    where T : IAdditionOperators<T, T, T>, IAdditiveIdentity<T, T>
{
    public static Point<T> operator +(Point<T> left, Translation<T> right) =>
        left with { X = left.X + right.XOffset, Y = left.Y + right.YOffset };
}

이 샘플에서는 제네릭 수학용 인터페이스가 어떻게 작성되는지 살펴보았습니다. 당신은 다음을 배우셨습니다:

  • INumber<T> 인터페이스에 의존하여 모든 숫자 형식에서 사용할 수 있는 메서드를 작성합니다.
  • 추가 인터페이스를 사용하는 형식을 빌드하여 하나의 수학 연산만 지원하는 형식을 구현합니다. 이 형식은 다른 방법으로 작성할 수 있도록 동일한 인터페이스에 대한 지원을 선언합니다. 알고리즘은 수학 연산자의 가장 자연스러운 구문을 사용하여 작성됩니다.

이러한 기능을 실험하고 피드백을 등록합니다. Visual Studio에서 피드백 보내기 메뉴 항목을 사용하거나 GitHub의 roslyn 리포지토리에서 새 문제를 만들 수 있습니다. 모든 숫자 형식으로 작동하는 제네릭 알고리즘을 빌드합니다. 형식 인수가 숫자와 유사한 기능의 하위 집합만 구현하는 이러한 인터페이스를 사용하여 알고리즘을 빌드합니다. 이러한 기능을 사용하는 새 인터페이스를 빌드하지 않더라도 알고리즘에서 이를 사용하여 실험해 볼 수 있습니다.

참고하십시오