'static abstract'를 이용해서 코딩하기

C# 10 부터 인터페이스에 메소드나 속성에 ‘static abstract’ 키워드를 적용하게 되면 인터페이스로 정적 오퍼레이터 메소드에 접근할 수 있게 되어 제네릭을 통한 연산이 가능하게 됩니다.

TValue Sum<TValue>(TValue a, TValue b) where TValue: INumber<TValue> => a + b;

제네릭 연산 말고 어떤 곳에서 또 유용하게 사용 가능할까요?

인터페이스에 static abstract 키워드로 메소드나 속성을 만들었다면, 인터페이스를 구현하는 클래스에서 반드시 해당 메소드나 속성을 구현해줘야 합니다.

interface ICreatable
{
    static abstract Driver Create(string driverName);
}

interface IDriver : ICreatable
{
    string Name { get; }
}

abstract class Driver : IDriver
{
    private static readonly Driver Empty = new EmptyDriver();

    public abstract string Name { get; }

    public static Driver Create(string driverName) => driverName switch
    {
        "A" => new ADriver(),
        "B" => new BDriver(),
        _ => Empty
    };
}

class EmptyDriver : Driver
{
    public override string Name => "Empty";
}

class ADriver : Driver
{
    public override string Name => "A";
}

class BDriver : Driver
{
    public override string Name => "B";
}

사용은,

var a = Driver.Create("A");
var b = Driver.Create("B");
var c = Driver.Create("C");

Console.WriteLine(a.Name);
Console.WriteLine(b.Name);
Console.WriteLine(c.Name);

여기서 static abstractabstract가 들어갔기 때문에 추상화가 가능한 것 처럼 보이지만, 그렇지 않습니다. 여기서 abstract의 의미는 “구현해야 하는” 정도로 해석하는게 맞습니다. 실제로 다음처럼 Driver의 Create()abstract를 넣으면 하면 오류가 발생합니다.

public static abstract Driver Create(string driverName); // 오류 발생
// CS0112: 'abstract' 정적 멤버는 override, virtual 또는 abstract로 표시할 수 없습니다.

흠, 기능적으로는 이 메소드를 최종 구현하는 자식 클래스에서 static override Driver Create()로 구현해줘도 혼란이 없을 것 같은데, 일단은 이 방식을 막았습니다. abstract - override 키워드가 주는 다형성의 느낌때문인것 같은데, 기능적으로는 문제될게 없기 때문에 차후에 추가되기를 기대해 봅니다.

다음으로 인터페이스 인스턴스로는 Create()메소드에 접근할 수 없습니다.

var ia = a as IDriver;
Console.WriteLine(ia.Name);
// ia.Create("D"); // 인터페이스형의 인스턴스로는 접근 안됨

이것은 어찌보면 당연한게, static abstract로 정의된 메소드에 접근하기 위해 개체지향의 다형성(vtable 접근 방식)으로 호출하는게 아니기 때문입니다.

하지만 다음은 허용합니다.

var a2 = DriverExtention.Create(a);

static class DriverExtention
{
    public static Driver Create<T>(T otherDriver) where T : IDriver => T.Create(otherDriver.Name);
}

이것이 가능한 이유는 제네릭 T가 컴파일 타임 때 결정되기 때문입니다.

3개의 좋아요

이 기능이 어떤 의도로 만들어졌을까요?
인터페이스에 정적 메소드…라는 것이 잘 감이 오질 않네요…

분명 유용할 것 같은데 예시로 들어주신 INumber와 같은 상황에서 사용하기 위한 목적일까요? :thinking:

3개의 좋아요

예 일단은 스칼라형을 제네릭으로 접근하기 위해서는 연산자에 접근해야 하는데 연산자가 static이라 불가능 했습니다. 그래서 'static abstract’가 추가된게 아닌가 추측은 해봅니다. C# 10이 출시되면 제네릭을 이용해 INumber 인터페이스로 메소드 등에서 연산을 할 수 있게 됩니다. 관련된 장점은 제 다른 글 댓글의 정성태님 링크를 참조하면 도움이 됩니다.

명세의 의미로도 쓰일 수 있습니다. 가령,

Parse라는 메소드의 경우 대체로 문자열을 받아서 자신의 인스턴스를 반환하는 정적 매소드인데 기존에는 이것을 명세 방법이 없었습니다. 'static abstract’를 사용하면 명세가 가능해집니다.

4개의 좋아요

저도 사실 level120 님 말씀처럼 인터페이스에 정적 메서드를 추가하는 개념은 잘 이해되지는 않습니다.
뭐…새로운 문법을 익혀서 개발 생산성을 좋게하고 소스코드 구조적으로 만드는 것은 좋겠지만, 인터페이스는 하나의 껍데기라는 개념에서 뭔가 이거저거 기능이 확장되면서 고전적인 객체지향에서의 인터페이스 개념이 흐려지는 것은 아닐까 걱정도 되네요.

쓰기 편한게 기준이라면야 무조건 적으로 좋은거겠지만요 ㅎㅎ 뭐 어차피 문법이라는거 쓸 사람은 쓰고 모르는 사람은 못 쓰고 그런거지만요!

3개의 좋아요

C# 이 계속해서 변화하고 있어서 어떤면에선 @Vincent 님의 말씀이 맞습니다. 다행인건 변화의 중요 고려가 호환성이기 때문에 필요성을 느끼지 않는다면 안쓰면 되는데, 협업시 문제가 되기는 할 것 같군요. 이것은 비단 C# 뿐만 아니라 발전하는 언어에 모두 해당되는것 같습니다. 이런 경우 적절히 적용 버젼을 약속하는게 방법이 될 것 같습니다.

저는 변화에 흥미로운 편인것 같습니다. 다양한 스칼라형을 인자로 받는 기능을 구현해본 경험이 만약 있다면 static abstract 및 INumber 가 매우 반가울 수 있지 않을까 하네요.

5개의 좋아요

저도 변화에 흥미로운 편입니다 ㅎㅎ 발전이 거듭되고 그게 시간이 지나면 나중에는 지금의 변화들이 기본이 되는 때도 있을테니… C#의 변화가 요즘 언어들과 추세를 비슷하게 가는 느낌을 받고 있었는데 다른언어들에도 인터페이스에 이런 다양한 기능이 추가되고 있는지 궁금하기도 합니다ㅎㅎ

4개의 좋아요