openlang

C#에서 setter 접근이 제한된 속성으로는 왜 인터페이스를 구현할 수 없나요?

클래스에 protected 속성을 만들려고 합니다. 그 클래스가 구현하는 인터페이스에도 같은 속성을 선언했습니다.

public interface IReading<T> {
    public T Adjustment { get; protected set; }
}

public class Reading : IReading<float> {
    public float Adjustment { get; protected set; } = 0.0f;
}

그런데 컴파일러가 다음 오류를 냅니다:

'Reading' does not implement interface member 'IReading.Adjustment.set'. 'Reading.Adjustment.set' is not public.

CS0277 (https://msdn.microsoft.com/query/roslyn.query?appId=roslyn&k=k(CS0277))

  • 클래스 속성에서 접근 한정자를 빼면 오류가 사라집니다

  • 인터페이스 속성에서 접근 한정자를 빼도 달라지지 않습니다

  • 양쪽 모두에서 접근 한정자를 빼면 오류가 사라집니다

C#에서는 이런 구성을 표현할 수 없다는 뜻으로 받아들이고 있습니다. 그래도 IReading 인터페이스의 모든 구현에서 이 속성을 protected 멤버로 구현하도록 요구할 수 없는 이유가 정말 궁금합니다.

비슷한 문제 같아서 비공개 인터페이스 멤버를 암시적으로 구현할 수 없는 이유에 대한 질문 (https://stackoverflow.com/q/68592353)도 봤지만, 여기서 제 구현이 암시적이라고 생각하지는 않습니다.

영어에서 번역원문 보기

이어지는 대화 2

서로의 근거를 따라
  1. 고유 링크

    아쉽지만 인터페이스 멤버의 protected는 기대하시는 방식으로 동작하지 않습니다.

    인터페이스 멤버에 쓰기에는 다소 특이한 한정자지만 C# 명세에는 설명이 조금 있습니다 (적어도 v8 초안 (https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/basic-concepts.md#754-protected-access)에는요):

    접근성이 protected 또는 private protected인 인터페이스 인스턴스 멤버는 그 인터페이스를 구현하는 클래스나 구조체에서 접근할 수 없으며, 그 인터페이스를 상속하는 인터페이스에서만 접근할 수 있습니다.

    이 차이는 인터페이스 멤버의 기본 구현에서 의미가 있습니다. 예를 들면 이렇습니다 (제네릭 매개변수는 여기서 영향을 주지 않아 뺐습니다):

    public interface IReading {
        public float Adjustment { get; protected set; }
    }
    
    public interface IExtendedReading : IReading {
        public float AdvanceAdjustment() => Adjustment++;
    }

    구체 클래스에서 명시적 구현을 사용하면 setter를 public으로 노출하지 않을 수 있습니다. 자동 구현 속성을 쓸 수도 있고요:

    public class Reading : IReading {
        float IReading.Adjustment { get; set; }
    }

    ... 또는 접근자를 전부 구현할 수도 있습니다:

    public class Reading : IReading {
        private float adjustment;
        float IReading.Adjustment {
            get => adjustment;
            set => adjustment = value;
        }
    }

    그러면 다른 클래스에 setter가 public으로 노출되지는 않지만, 그래도 원하시는 동작을 달성하는 것은 아닙니다.

    이 경우에는 인터페이스보다 추상 클래스가 더 나을 수 있습니다.

    영어에서 번역원문 보기
  2. 고유 링크

    ed_fern의 설명이 이 부분을 잘 다룹니다. 그래서 같은 설명을 되풀이하지는 않겠습니다.

    인터페이스에는 getter만 있는 속성을 두는 것을 권합니다. 한정자가 protected인 경우를 포함해 어차피 공개적으로 보이는 부분은 그것입니다. 그러면 구현 쪽에서 원하는 접근성의 setter를 추가할 수 있습니다 (private, private protected, protected 또는 public (한정자 없이 set;만 쓰는 것) 중에서 고르거나 set; 대신 init;를 쓸 수도 있습니다.

    그 속성에 required 한정자를 붙이는 방법도 있습니다. 그렇게 해도 인터페이스 계약을 위반하지 않습니다.

    public interface IReading<T> {
        public T Adjustment { get; }
    }
    
    public class Reading : IReading<float> {
        public float Adjustment { get; protected set; } = 0.0f;
    }
    영어에서 번역원문 보기

같은 질문의 다른 갈래

  1. Godot 3.3의 GDScript에서 일일이 나열하지 않고 enum 항목을 무작위로 고르기?

    GDScript에서 enum을 이렇게 선언했어요: 가능한 모든 변형을 나열하지 않고 enum 변형을 무작위로 고르고 싶어요. 나중에 변형을 더 추가해도 무작위로 선택하는 코드를 수정하지 않도록요. 이런 방법을 시도해 봤어요:…

    답글 2최근 활동 gdscript
  2. Nim 나머지 연산 문법

    Nim에서는 나머지를 어떻게 표현하나요? cell % 7 == 0를 쓰면 undeclared identifier: '%'가 나옵니다 Google로 검색해도 SO를 찾아봐도 답을 찾지 못했습니다.

    답글 2최근 활동 nim
  3. APL 함수에서 결과 반환하기

    함수 실행이 끝나면 어떤 값을 반환하게 하려면 어떻게 하나요? 예를 들어 이런 것이 있습니다: ROUTINE N ? N ⍴ 12 //이것은 1부터 12까지의 난수 N개를 만듭니다 (N은 함수의 인수입니다). 함수의 결과를 돌…

    답글 2최근 활동 apl