C#8.0 기본 인터페이스 구현의 세부 사례

Intro


C#8.0은 기본 인터페이스 구현, 즉 인터페이스에서 쓰기 가능한 방법을 도입하기 시작했다.
이전 버전에서 인터페이스는 정의하여 실현할 방법이 없고 방법도 모두 공공적이다. 인터페이스와 속성을 제외하고는 다른 데이터를 정의할 수 없다. 이것은 인터페이스가 처음부터 비교적 잘 설계되어야 한다는 것을 의미한다. 그렇지 않으면 기존 인터페이스에 새로운 방법을 추가할 때 그 실현은 반드시 수정해야 한다. 그렇지 않으면 컴파일이 실패할 것이다.기본 인터페이스 구현은 파괴적인 변경을 일으키지 않는 전제에서 인터페이스에 새로운 방법을 추가할 수 있으며, 인터페이스에 기본적인 실현만 제공하면 된다.

Sample


다음 예제를 살펴보겠습니다.

internal interface IFly
{
    string Name { get; }
}
internal class Superman : IFly
{
    public string Name => nameof(Superman);
}
internal class MonkeyKing : IFly
{
    public string Name => nameof(MonkeyKing);
}
이것은 기본적인 인터페이스 정의이고 두 가지 실현을 제공했다. 이어서 우리는 인터페이스에 새로운 방법을 추가했다.

internal interface IFly
{
    string Name { get; }

    void Fly() => Console.WriteLine($"{Name.GetValueOrDefault((DefaultName))} is flying");
}

internal class Superman : IFly
{
    public string Name => nameof(Superman);

    public void Test()
    {
        ((IFly) this).Fly();
        Console.WriteLine(Name);
    }
}

internal class MonkeyKing : IFly
{
    public string Name => nameof(MonkeyKing);

    public void Fly()
    {
        Console.WriteLine($"I'm {Name}, I'm flying");
    }
}

우리는 인터페이스에 Fly 방법을 추가하고 기본 실현을 제공했다. 그 중 하나를 다시 썼다. 코드를 써서 테스트해 보자.

// Cannot resolve symbol 'Fly'
// new Superman().Fly();

IFly fly = new Superman();
fly.Fly();

fly = new MonkeyKing();
fly.Fly();

출력 결과는 다음과 같습니다.
Superman is flying
I'm MonkeyKing, I'm flying
IFly
위의 예시에서 슈퍼맨은 Fly라는 방법을 정의하지 않았다. Fly 방법을 직접 호출할 수 없기 때문에 IFly 인터페이스로 전환한 다음에 호출해야 한다. 이때 방법은 인터페이스에서 정의된 논리이고 몽키킹은 Fly 방법을 실현했기 때문에 자신의 Fly를 사용한다. 위에서 보듯이
위의 기본 사용법을 제외하고는 이제 인터페이스에서 정적 필드 정적 방법을 정의하여 더 좋은 방법을 복원할 수 있습니다. 우리는 위의 예시에서 수정된 예시를 다음과 같이 보여 드리겠습니다.

internal interface IFly
{
    private const string DefaultName = nameof(IFly);

    protected static string GetDefaultName() => DefaultName;

    public static string GetPublicName() => DefaultName;

    // Interface cannot contain instance fields
    // private string name = "";

    string Name { get; }

    void Fly() => Console.WriteLine($"{Name.GetValueOrDefault((DefaultName))} is flying");
}

internal class MonkeyKing : IFly
{
    public string Name => nameof(MonkeyKing);

    public void Fly()
    {
        Console.WriteLine($"I'm {Name}, I'm flying, DefaultName:{IFly.GetDefaultName()}");
    }
}

protected static의 방법이나 필드를 정의하면 인터페이스를 실현하는 클래스에서 IFly를 통과할 수 있습니다.GetDefaultName () 을 사용하여 인터페이스를 호출하는 방법입니다. protected라면 인터페이스를 실행하는 형식에서만 사용할 수 있습니다. 인터페이스를 실행하지 않은 형식에서public로 호출하면 됩니다. 다음은 인터페이스를 실행하지 않은 형식에서 호출하는 예입니다.
//Cannot access protected method 'GetDefaultName' here
//IFly.GetDefaultName().Dump();
IFly.GetPublicName().Dump();

More


지금은 이렇게 사용할 수 있지만 저는 개인적으로 이전의 인터페이스 사용법을 그대로 사용하는 것을 추천합니다. 이 특성을 쉽게 사용하지 말고 미리 설계하고 미리 기획하는 것이 올바른 것입니다. 사후 보상을 생각하지 마세요. 이 특성이 비교적 적합한 사용 장면은 현재 인터페이스를 바탕으로 하는 확장 방법이고 확장 방법은 하나의 인터페이스의 기본적인 실현으로서 구체적인 유형은 이 실현을 다시 쓸 수 있습니다. 사용 예는 다음과 같습니다.

Task<bool> SaveProperties(int id, Dictionary<string, object> properties)
{
    if (properties is null || properties.Count == 0) return Task.FromResult(false);
    var json = JsonConvert.SerializeObject(properties.Select(p => new PropertyModel()
    {
        PropertyName = p.Key,
        PropertyValue = p.Value?.ToString()
    }));
    return SaveProperties(id, json);
}

Task<bool> SaveProperties(int id, string properties);

이전 버전에서 저는 보통 위의 방법을 확장 방법으로 사용했습니다. 기본 인터페이스가 실현된 후에도 기본 실현을 고려할 수 있습니다. (업무 코드에만 한정되어 있고 라이브러리 코드에 대한 느낌은 깨끗할수록 좋습니다.)
References
  • https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/default-interface-methods-versions
  • https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-8#default-interface-methods
  • https://github.com/WeihanLi/SamplesInPractice/blob/master/CSharp9Sample/DefaultInterfaceImplement.cs
  • 총결산


    이 C#8.0 기본 인터페이스 구현에 관한 글은 여기 소개되어 있습니다. 더 많은 C#8.0 기본 인터페이스 내용은 저희 이전의 글을 검색하거나 아래의 관련 글을 계속 훑어보십시오. 앞으로 많은 응원 부탁드립니다!

    좋은 웹페이지 즐겨찾기