다음을 통해 공유


기본 인터페이스 메서드

메모

이 문서는 기능 사양입니다. 사양은 기능의 디자인 문서 역할을 합니다. 여기에는 기능 디자인 및 개발 중에 필요한 정보와 함께 제안된 사양 변경 내용이 포함됩니다. 이러한 문서는 제안된 사양 변경이 완료되고 현재 ECMA 사양에 통합될 때까지 게시됩니다.

기능 사양과 완료된 구현 간에 약간의 불일치가 있을 수 있습니다. 이러한 차이는관련 LDM(언어 디자인 모임) 노트에서 캡처됩니다.

사양문서에서 기능 명세서를 C# 언어 표준으로 채택하는 프로세스에 대해 더 알아볼 수 있습니다.

챔피언 이슈: https://github.com/dotnet/csharplang/issues/52

요약

구체적인 구현을 사용하는 인터페이스의 메서드인 가상 확장 메서드에 대한 지원을 추가합니다. 이러한 인터페이스를 구현하는 클래스 또는 구조체는 인터페이스 메서드에 대해 클래스 또는 구조체에 의해 구현되거나 기본 클래스 또는 인터페이스로부터 상속된 단일 가장 구체적인 구현을 가져야 합니다. 가상 확장 메서드를 사용하면 API 작성자가 해당 인터페이스의 기존 구현과 소스 또는 이진 호환성을 손상하지 않고 이후 버전의 인터페이스에 메서드를 추가할 수 있습니다.

이는 Java의 "기본 메서드"비슷합니다.

(가능한 구현 기술에 따라) 이 기능을 사용하려면 CLI/CLR에서 해당 지원이 필요합니다. 이 기능을 활용하는 프로그램은 이전 버전의 플랫폼에서 실행할 수 없습니다.

동기

이 기능의 주요 동기는 다음과 같습니다.

  • 기본 인터페이스 메서드를 사용하면 API 작성자가 해당 인터페이스의 기존 구현과 소스 또는 이진 호환성을 손상하지 않고 이후 버전의 인터페이스에 메서드를 추가할 수 있습니다.
  • 이 기능을 통해 C#은 유사한 기능을 지원하는 Android(Java)iOS(Swift)대상으로 하는 API와 상호 운용할 수 있습니다.
  • 알고 보니 기본 인터페이스 구현을 추가하면 "특성" 언어 기능(https://en.wikipedia.org/wiki/Trait_(computer_programming))의 요소가 제공됩니다. 특성은 강력한 프로그래밍 기술(http://scg.unibe.ch/archive/papers/Scha03aTraits.pdf)으로 입증되었습니다.

상세 디자인

인터페이스의 구문이 무언가를 허용할 수 있도록 확장됩니다.

  • 상수, 연산자, 정적 생성자 및 중첩 형식을 선언하는 멤버 선언
  • 메서드 또는 인덱서, 속성 또는 이벤트 접근자에 대한 본문(즉, "기본" 구현).
  • 정적 필드, 메서드, 속성, 인덱서 및 이벤트를 선언하는 멤버 선언
  • 명시적 인터페이스 구현 구문을 사용하는 멤버 선언; 그리고
  • 명시적 액세스 한정자(기본 액세스는 public)입니다.

본문을 가진 멤버는 인터페이스가 자체 구현을 제공하지 않는 클래스 및 구조체에서 메서드의 "기본" 구현을 제공할 수 있도록 허용합니다.

인터페이스에는 인스턴스 상태가 포함될 수 없습니다. 이제 정적 필드가 허용되지만 인터페이스에서는 인스턴스 필드가 허용되지 않습니다. 인스턴스 자동 속성은 숨겨진 필드를 암시적으로 선언하므로 인터페이스에서 지원되지 않습니다.

정적 및 프라이빗 메서드는 인터페이스의 공용 API를 구현하는 데 사용되는 코드의 유용한 리팩터링 및 구성을 허용합니다.

인터페이스에서 메서드 재정의는 명시적 인터페이스 구현 구문을 사용해야 합니다.

variance_annotation로 선언된 형식 매개 변수의 범위 내에서 클래스 형식, 구조체 형식, 또는 열거형 형식을 선언하는 것은 오류입니다. 예를 들어 아래 C 선언은 오류입니다.

interface IOuter<out T>
{
    class C { } // error: class declaration within the scope of variant type parameter 'T'
}

인터페이스의 구체적인 메서드

이 기능의 가장 간단한 형태는 본문이 있는 메서드인 인터페이스에서 구체적인 메서드를 선언하는 기능입니다.

interface IA
{
    void M() { WriteLine("IA.M"); }
}

이 인터페이스를 구현하는 클래스는 구체적인 메서드를 구현할 필요가 없습니다.

class C : IA { } // OK

IA i = new C();
i.M(); // prints "IA.M"

클래스 IA.M 내의 C에 대한 최종 재정의는 M에서 선언된 구체적인 메서드 IA입니다. 클래스는 해당 인터페이스에서 멤버를 상속하지 않습니다. 이 점은 이 기능에 의해 변경되지 않습니다.

new C().M(); // error: class 'C' does not contain a member 'M'

인터페이스의 인스턴스 멤버 내에서 this 바깥쪽 인터페이스의 형식을 가합니다.

인터페이스의 수정자

인터페이스의 구문은 멤버에 대한 한정자를 허용하도록 완화됩니다. private, protected, internal, public, virtual, abstract, sealed, static, externpartial허용됩니다.

virtual 또는 sealed 한정자를 사용하지 않는 한 선언에 본문이 포함된 인터페이스 멤버는 private 멤버입니다. virtual 한정자는 암시적으로 virtual함수 멤버에서 사용될 수 있습니다. 마찬가지로, abstract은 본문이 없는 인터페이스 멤버의 기본값이지만, 이 한정자는 명시적으로 지정할 수도 있습니다. 가상이 아닌 멤버는 sealed 키워드를 사용하여 선언할 수 있습니다.

인터페이스의 private 또는 sealed 함수 멤버에 본문이 없는 것은 오류입니다. private 함수 멤버에 한정자 sealed없을 수 있습니다.

액세스 한정자는 허용되는 모든 종류의 멤버의 인터페이스 멤버에서 사용할 수 있습니다. 액세스 수준 public 기본값이지만 명시적으로 제공될 수 있습니다.

오픈 이슈: 우리는 protectedinternal와 같은 액세스 한정자의 정확한 의미와 (파생된 인터페이스 내에서) 이를 재정의하거나 (인터페이스를 구현하는 클래스에서) 구현하는 선언들을 명확히 지정해야 합니다.

인터페이스는 중첩된 형식, 메서드, 인덱서, 속성, 이벤트 및 정적 생성자를 포함하여 static 멤버를 선언할 수 있습니다. 모든 인터페이스 멤버에 대한 기본 액세스 수준은 public.

인터페이스는 인스턴스 생성자, 소멸자 또는 필드를 선언할 수 없습니다.

닫힌 문제: 인터페이스에서 연산자 선언을 허용해야 하나요? 아마도 변환 연산자는 아니지만, 다른 연산자는 어떻습니까? 의사 결정: 연산자는 변환, 같음 및 같지 않음 연산자 제외하고 허용됩니다.

해결된 문제: 기본 인터페이스에서 멤버를 숨기는 인터페이스 멤버 선언에 대해 new를 허용해야 하나요? 결정: 예.

닫힌 문제: 현재 인터페이스 또는 해당 멤버에 대한 partial 허용하지 않습니다. 이를 위해서는 별도의 제안이 필요합니다. 결정: 예. https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#permit-partial-in-interface

인터페이스의 명시적 구현

명시적 구현을 사용하면 프로그래머가 컴파일러 또는 런타임에서 가상 멤버를 찾을 수 없는 인터페이스에서 가상 멤버의 가장 구체적인 구현을 제공할 수 있습니다. 구현 선언은 인터페이스 이름으로 선언을 한정하여 특정 기본 인터페이스 메서드를 구현하기 명시적으로 수 있습니다(이 경우 액세스 한정자가 허용되지 않음). 암시적 구현은 허용되지 않습니다.

interface IA
{
    void M() { WriteLine("IA.M"); }
}
interface IB : IA
{
    void IA.M() { WriteLine("IB.M"); } // Explicit implementation
}
interface IC : IA
{
    void M() { WriteLine("IC.M"); } // Creates a new M, unrelated to `IA.M`. Warning
}

명시적 구현은 인터페이스 내에서 선언할 수 없습니다. sealed

인터페이스의 공용 virtual 함수 멤버는 파생된 인터페이스에서만 명시적으로 구현될 수 있습니다(선언의 이름을 원래 메서드를 선언한 인터페이스 형식으로 한정하고 액세스 한정자를 생략). 멤버는 구현되는 곳에서 접근 가능해야 합니다.

재추상화

인터페이스에 선언된 가상(구체적인) 메서드는 파생된 인터페이스에서 다시 삭제될 수 있습니다.

interface IA
{
    void M() { WriteLine("IA.M"); }
}
interface IB : IA
{
    abstract void IA.M();
}
class C : IB { } // error: class 'C' does not implement 'IA.M'.

abstract가 재추상화되고 있음을 나타내기 위해, IB.M 한정자는 IA.M의 선언에서 필요합니다.

이는 메서드의 기본 구현이 적절하지 않고 클래스를 구현하여 보다 적절한 구현을 제공해야 하는 파생 인터페이스에서 유용합니다.

가장 구체적인 구현 규칙

모든 인터페이스와 클래스에는 형식 또는 직접 및 간접 인터페이스에 표시되는 구현 중 모든 가상 멤버에 대한 가장 구체적인 구현 있어야 합니다. 가장 구체적인 구현 다른 모든 구현보다 더 구체적인 고유한 구현입니다. 구현이 없는 경우 멤버 자체는 가장 구체적인 구현으로 간주됩니다.

한 구현 형식 선언되고 형식 선언된 경우 다른 구현보다 더 구체적인 간주됩니다.

  1. T1는 직접 또는 간접 인터페이스 중 T2을 포함하거나
  2. T2 인터페이스 형식이지만 T1 인터페이스 형식이 아닙니다.

예를 들어:

interface IA
{
    void M() { WriteLine("IA.M"); }
}
interface IB : IA
{
    void IA.M() { WriteLine("IB.M"); }
}
interface IC : IA
{
    void IA.M() { WriteLine("IC.M"); }
}
interface ID : IB, IC { } // compiles, but error when a class implements 'ID'
abstract class C : IB, IC { } // error: no most specific implementation for 'IA.M'
abstract class D : IA, IB, IC // ok
{
    public abstract void M();
}
public class E : ID { } // Error. No most specific implementation for 'IA.M'

가장 구체적인 구현 규칙은 충돌이 발생하는 지점에서 프로그래머가 충돌(즉, 다이아몬드 상속에서 발생하는 모호성)을 명시적으로 해결하도록 합니다.

인터페이스에서 명시적 재추상화를 지원하므로 클래스에서도 이를 수행할 수 있습니다.

abstract class E : IA, IB, IC // ok
{
    abstract void IA.M();
}

닫힌 문제: 클래스에서 명시적 인터페이스 추상 구현을 지원해야 하나요? 결정: 없음

또한 클래스 선언에서 일부 인터페이스 메서드의 가장 구체적인 구현이 인터페이스에서 선언된 추상 구현인 경우 오류가 발생합니다. 새 용어를 사용하여 다시 설명하는 기존 규칙입니다.

interface IF
{
    void M();
}
abstract class F : IF { } // error: 'F' does not implement 'IF.M'

인터페이스에 선언된 가상 속성은 하나의 인터페이스에서 get 접근자에 대한 가장 구체적인 구현과 다른 인터페이스의 set 접근자에 대한 가장 구체적인 구현을 가질 수 있습니다. 이는 가장 구체적인 구현 규칙을 위반하는 것으로 간주되며 컴파일러 오류를 생성합니다.

staticprivate 메서드

이제 인터페이스에 실행 코드가 포함될 수 있으므로 공통 코드를 프라이빗 및 정적 메서드로 추상화하면 유용합니다. 이제 인터페이스에서 이를 허용합니다.

닫힌 문제: 프라이빗 메서드를 지원해야 하나요? 정적 메서드를 지원해야 하나요? 결정: 예

오픈 이슈: 인터페이스 메서드에 대해 protected 또는 internal 또는 기타 접근을 허용할 것인가요? 그렇다면 의미 체계는 무엇인가요? 그들은 기본적으로 virtual인가요? 그렇다면 가상이 아닌 방법으로 만들 수 있나요?

닫힌 문제: 정적 메서드를 지원하는 경우 (정적) 연산자를 지원해야 하나요? 결정: 예

기본 인터페이스 호출

이 섹션의 구문은 구현되지 않았습니다. 그것은 여전히 활성 제안으로 남아있다.

기본 메서드를 사용하는 인터페이스에서 파생되는 형식의 코드는 해당 인터페이스의 "기본" 구현을 명시적으로 호출할 수 있습니다.

interface I0
{
   void M() { Console.WriteLine("I0"); }
}
interface I1 : I0
{
   override void M() { Console.WriteLine("I1"); }
}
interface I2 : I0
{
   override void M() { Console.WriteLine("I2"); }
}
interface I3 : I1, I2
{
   // an explicit override that invoke's a base interface's default method
   void I0.M() { I2.base.M(); }
}

인스턴스(비정적) 메서드는 구문 base(Type).M을 사용하여 이름을 지정함으로써 기본 인터페이스에서 액세스할 수 있는 인스턴스 메서드의 구현을 비가상으로 호출할 수 있습니다. 다이아몬드 상속으로 인해 필요한 재정의를 하나의 특정 기본 구현에 위임하여 해결해야 할 때 유용합니다.

interface IA
{
    void M() { WriteLine("IA.M"); }
}
interface IB : IA
{
    override void IA.M() { WriteLine("IB.M"); }
}
interface IC : IA
{
    override void IA.M() { WriteLine("IC.M"); }
}

class D : IA, IB, IC
{
    void IA.M() { base(IB).M(); }
}

구문 virtual를 사용하여 abstract 또는 base(Type).M 멤버에 액세스하는 경우, Type에 대한 고유한 M를 포함해야 합니다.

바인딩 기본 항목들

이제 인터페이스에 형식이 포함됩니다. 이러한 형식은 기본 절에서 기본 인터페이스로 사용할 수 있습니다. 기본 절을 바인딩할 때 이러한 형식을 바인딩하는 기본 인터페이스 집합을 알고 있어야 할 수 있습니다(예: 해당 형식을 조회하고 보호된 액세스를 해결하려면). 따라서 인터페이스의 기본 절의 의미는 순환적으로 정의됩니다. 주기를 중단하기 위해 클래스에 이미 있는 유사한 규칙에 해당하는 새 언어 규칙을 추가합니다.

인터페이스의 interface_base 의미를 확인하는 동안 기본 인터페이스는 일시적으로 비어 있는 것으로 간주됩니다. 직관적으로 이렇게 하면 기본 절의 의미가 재귀적으로 자체적으로 의존할 수 없습니다.

예전에는 다음과 같은 규칙이

"클래스 B가 클래스 A에서 파생되는 경우 A가 B에 의존하는 것이 컴파일 시간 오류입니다. 직접 클래스는 직접 기본 클래스(있는 경우)를 따라 달라지며, 바로 중첩되는 클래스 따라 달라집니다(있는 경우). 이 정의를 감안할 때 클래스가 의존하는 클래스의 전체 집합은 반사적이고 전이적인 폐쇄는 관계에 따라 직접 달라집니다."

인터페이스가 직접 또는 간접적으로 스스로 상속하는 것은 컴파일 시간 오류입니다. 인터페이스의에서 기본 인터페이스는 명시적으로 정의된 기본 인터페이스와 그들의 기본 인터페이스입니다. 즉, 기본 인터페이스 집합은 명시적인 기본 인터페이스와 그들의 명시적 기본 인터페이스 등으로 이루어진 완전한 전이적 닫힘입니다.

다음과 같이 조정하고 있습니다.

클래스 B가 클래스 A에서 파생되는 경우 A가 B에 의존하는 것은 컴파일 시간 오류입니다. 클래스 직접 직접 기본 클래스(있는 경우)에 따라 달라지며, 바로 중첩되는 형식 따라 달라집니다(있는 경우).

인터페이스 IB가 인터페이스 IA를 확장하는 경우 IA가 IB에 의존하는 것은 컴파일 시간 오류입니다. 인터페이스 은(는) 직접 기본 인터페이스(있는 경우)에 의존하며, 해당 인터페이스가 즉시 중첩된 형식(있는 경우)에 의존합니다.

이러한 정의를 고려할 때 형식이 의존하는 형식의 전체 집합은 관계에 따라 직접 반사적이고 전이적인 닫기입니다.

기존 프로그램에 미치는 영향

여기에 제시된 규칙은 기존 프로그램의 의미에 영향을 주지 않습니다.

예제 1:

interface IA
{
    void M();
}
class C: IA // Error: IA.M has no concrete most specific override in C
{
    public static void M() { } // method unrelated to 'IA.M' because static
}

예제 2:

interface IA
{
    void M();
}
class Base: IA
{
    void IA.M() { }
}
class Derived: Base, IA // OK, all interface members have a concrete most specific override
{
    private void M() { } // method unrelated to 'IA.M' because private
}

동일한 규칙은 기본 인터페이스 메서드와 관련된 유사한 상황과 유사한 결과를 제공합니다.

interface IA
{
    void M() { }
}
class Derived: IA // OK, all interface members have a concrete most specific override
{
    private void M() { } // method unrelated to 'IA.M' because private
}

닫힌 문제: 사양의 의도된 결과인지 확인합니다. 결정: 예

런타임 메서드 결정

닫힌 문제: 사양은 인터페이스 기본 메서드에 대해 런타임 메서드 해석 알고리즘을 설명해야 합니다. 의미 체계가 언어 의미 체계와 일치하는지 확인해야 합니다. 예를 들어 선언된 메서드는 internal 메서드를 재정의하거나 구현하지 않습니다.

CLR 지원 API

컴파일러가 이 기능을 지원하는 런타임에 대해 컴파일하는 시기를 감지하기 위해 이러한 런타임에 대한 라이브러리는 https://github.com/dotnet/corefx/issues/17116설명된 API를 통해 해당 사실을 보급하도록 수정됩니다. 추가합니다.

namespace System.Runtime.CompilerServices
{
    public static class RuntimeFeature
    {
        // Presence of the field indicates runtime support
        public const string DefaultInterfaceImplementation = nameof(DefaultInterfaceImplementation);
    }
}

열려 있는 문제: CLR 기능에 가장 적합한 이름인가요? CLR 기능은 그 이상의 작업을 수행합니다(예: 보호 제약 조건을 완화하고 인터페이스에서 재정의를 지원하는 등). 아마도 "인터페이스의 구체적인 방법"이나 "특성"으로 이름을 붙여야 할까?

지정할 추가 영역

  • [ ] 기본 인터페이스 메서드와 재정의를 기존 인터페이스에 추가함으로써 발생하는 소스 및 바이너리 호환성 영향을 분류하는 것이 유용할 것입니다.

단점

이 제안에는 인터페이스의 구체적인 메서드와 메서드 해석을 지원하기 위해 CLR 사양에 대한 조정된 업데이트가 필요합니다. 따라서 비교적 "비용이 많이 들 수" 있으며, CLR 변경이 필요할 것으로 예상되는 다른 기능과 함께 수행하는 것이 가치가 있을 수 있습니다.

대안

없음.

해결되지 않은 질문

  • 공개 질문은 위의 제안 전반에 걸쳐 호출됩니다.
  • 열린 질문 목록은 https://github.com/dotnet/csharplang/issues/406 참조하세요.
  • 자세한 사양은 호출할 정확한 메서드를 선택하기 위해 런타임에 사용되는 확인 메커니즘을 설명해야 합니다.
  • 새 컴파일러에서 생성되고 이전 컴파일러에서 사용하는 메타데이터의 상호 작용을 자세히 해결해야 합니다. 예를 들어 사용하는 메타데이터 표현으로 인해 인터페이스에 기본 구현이 추가되어 이전 컴파일러에서 컴파일될 때 해당 인터페이스를 구현하는 기존 클래스가 중단되지 않도록 해야 합니다. 이는 사용할 수 있는 메타데이터 표현에 영향을 줄 수 있습니다.
  • 디자인에서는 다른 언어 및 다른 언어에 대한 기존 컴파일러와의 상호 운용을 고려해야 합니다.

해결된 질문

추상 재정의

이전 초안 사양에는 상속된 메서드를 "다시 사용"하는 기능이 포함되어 있습니다.

interface IA
{
    void M();
}
interface IB : IA
{
    override void M() { }
}
interface IC : IB
{
    override void M(); // make it abstract again
}

2017-03-20에 대한 내 노트는 우리가 이것을 허용하지 않기로 결정했다는 것을 보여주었습니다. 그러나 두 개 이상의 사용 사례가 있습니다.

  1. 이 기능의 일부 사용자가 상호 운용하기를 희망하는 Java API는 이 기능에 따라 달라집니다.
  2. 특성 을 사용하여 프로그래밍하면의 이점이 있습니다. 재추상화는 "특성" 언어 기능(https://en.wikipedia.org/wiki/Trait_(computer_programming))의 요소 중 하나입니다. 클래스에는 다음이 허용됩니다.
public abstract class Base
{
    public abstract void M();
}
public abstract class A : Base
{
    public override void M() { }
}
public abstract class B : A
{
    public override abstract void M(); // reabstract Base.M
}

아쉽게도 이 코드는 허용되지 않는 한 인터페이스 집합(특성)으로 리팩터링할 수 없습니다. 야렛의 탐욕원칙에 의해, 그것은 허용되어야 한다.

종결된 이슈: 재추상화가 허용되어야 하나요? [예] 내 노트가 잘못되었습니다. LDM 노트는 인터페이스에서 재추상화가 허용된다고 말합니다. 수업에 속해 있지 않습니다.

가상 한정자 대 봉인된 한정자

알렉세이 칭가우즈:

인터페이스 멤버에 대해 명시적으로 언급된 한정자를 허용하기로 결정했습니다. 다만, 일부 한정자를 허용하지 않아야 할 이유가 있는 경우는 제외합니다. 가상 한정자와 관련하여 흥미로운 질문이 제기됩니다. 기본 구현이 있는 멤버에 필요해야 하나요?

다음과 같이 말할 수 있습니다.

  • 구현이 없고 가상이나 봉인이 지정되지 않은 경우, 해당 멤버가 추상이라고 가정합니다.
  • 구현이 있고 추상 또는 봉인된 상태가 지정되지 않은 경우 멤버가 가상이라고 가정합니다.
  • 가상 또는 추상이 아닌 메서드를 만들기 위해서는 봉인된 한정자가 필요합니다.

또는 가상 멤버에 가상 한정자가 필요하다고 말할 수 있습니다. 즉, 구현이 명시적으로 가상 한정자로 표시되지 않은 멤버가 있는 경우 가상도 추상도 아닙니다. 이 방법은 메서드를 클래스에서 인터페이스로 이동할 때 더 나은 환경을 제공할 수 있습니다.

  • 추상 메서드는 추상 상태로 유지됩니다.
  • 가상 메서드는 가상 상태로 유지됩니다.
  • 한정자가 없는 메서드는 가상 또는 추상으로 유지되지 않습니다.
  • 봉인된 한정자는 재정의가 아닌 메서드에 적용할 수 없습니다.

어떻게 생각하세요?

닫힌 문제: 구현을 포함한 구체적인 방법이 암시적으로 virtual되어야 합니까? [예]

결정: LDM 2017-04-05에서 만든:

  1. 가상이 아닌 경우 sealed 또는 private통해 명시적으로 표현되어야 합니다.
  2. sealed은 본문을 가진 인터페이스 인스턴스 멤버를 가상이 아닌 상태로 만드는 키워드입니다.
  3. 인터페이스의 모든 한정자를 허용하기를 희망합니다.
  4. 인터페이스 멤버에 대한 기본 접근성은 중첩 형식을 포함하여 공용입니다.
  5. 인터페이스의 private 함수 멤버는 암시적으로 봉인되며 sealed 허용되지 않습니다.
  6. 프라이빗 클래스(인터페이스)가 허용되고 봉인될 수 있으며 이는 봉인된 클래스의 의미에서 봉인됨을 의미합니다.
  7. 좋은 제안이 없으므로 인터페이스 또는 해당 멤버에는 여전히 일부가 허용되지 않습니다.

이진 호환성 1

라이브러리가 기본 구현을 제공하는 경우

interface I1
{
    void M() { Impl1 }
}
interface I2 : I1
{
}
class C : I2
{
}

우리는 I1.M에서 C의 구현이 I1.M라는 것을 이해합니다. I2 포함하는 어셈블리가 다음과 같이 변경되고 다시 컴파일되면 어떻게 되나요?

interface I2 : I1
{
    override void M() { Impl2 }
}

하지만 C 다시 컴파일되지 않습니다. 프로그램을 실행하면 어떻게 되나요? (C as I1).M() 호출 작업

  1. 실행 I1.M
  2. 실행 I2.M
  3. 일종의 런타임 오류를 던집니다.

결정: 작성된 2017-04-11: 런타임에 명확하게 가장 구체적인 재정의가 되는 I2.M실행합니다.

이벤트 접근자(닫힘)

닫힌 문제: 이벤트를 "부분별로" 덮어쓸 수 있나요?

이 경우를 고려합니다.

public interface I1
{
    event T e1;
}
public interface I2 : I1
{
    override event T
    {
        add { }
        // error: "remove" accessor missing
    }
}

클래스에서와 같이 이벤트 선언의 구문은 하나의 접근자만 허용하지 않으므로 이벤트의 이 "부분" 구현은 허용되지 않습니다. 둘 다(또는 둘 다)를 제공해야 합니다. 구문에서 본문이 없을 경우 추상 제거 접근자 메서드가 암시적으로 추상이 되도록 허용해서 같은 작업을 수행할 수 있습니다.

public interface I1
{
    event T e1;
}
public interface I2 : I1
{
    override event T
    {
        add { }
        remove; // implicitly abstract
    }
}

이것은 새로운(제안된) 구문임을 유의하십시오. 현재 문법에서 이벤트 접근자에는 필수 코드 블록이 있습니다.

닫힌 문제: 인터페이스 및 속성 접근자의 메서드가 본문을 생략하여 (암시적으로) 추상화되는 방식과 유사하게, 본문을 생략하여 이벤트 접근자를 (암시적으로) 추상화할 수 있나요?

결정:(2017-04-18) 아니요, 이벤트 선언에는 구체적인 접근자(또는 둘 다)가 모두 필요합니다.

클래스에서 재추상화 (마감됨)

닫힌 문제: 이것이 허용되는지 확인해야 합니다. 그렇지 않으면 기본 구현을 추가하는 것은 호환성을 깨뜨리는 변화가 될 수 있습니다.

interface I1
{
    void M() { }
}
abstract class C : I1
{
    public abstract void M(); // implement I1.M with an abstract method in C
}

결정:(2017-04-18) 예, 인터페이스 멤버 선언에 본문을 추가해도 C에는 영향을 미치지 않습니다.

봉인된 우회(닫힘)

이전 질문에서는 인터페이스의 sealedoverride 수식어를 적용할 수 있다고 암묵적으로 가정하고 있습니다. 이는 초안 사양과 모순됩니다. 재정의를 봉인하는 것을 허용하시겠습니까? 봉인을 할 경우의 소스 및 바이너리 호환성 영향을 고려해야 합니다.

종결된 이슈: 오버라이드 봉인을 허용해야 하나요?

결정: (2017-04-18), 인터페이스의 재정의에 대한 sealed를 허용하지 맙시다. 인터페이스 멤버에서 sealed의 유일한 사용 목적은 초기 선언 시 해당 멤버를 가상 멤버가 아닌 것으로 만드는 것입니다.

다이아몬드 상속 및 클래스(닫힘)

제안 초안은 다이아몬드 상속 시나리오에서 인터페이스 재정의보다는 클래스를 재정의하는 것을 선호합니다.

모든 인터페이스와 클래스에는 해당 형식 또는 직접 및 간접 인터페이스에 나타나는 재정의 중에서 모든 인터페이스 메서드에 대해 가장 구체적인 재정의가 있어야 합니다. 가장 구체적인 재정의 다른 모든 재정의보다 더 구체적인 고유 재정의입니다. 재정의가 없으면, 메서드 자체가 가장 구체적인 재정의로 간주됩니다.

한 재정의 M1 형식 M2선언되고 M1 형식 T1선언된 경우 다른 재정의 M2T2 간주됩니다.

  1. T1는 직접 또는 간접 인터페이스 중 T2을 포함하거나
  2. T2 인터페이스 형식이지만 T1 인터페이스 형식이 아닙니다.

시나리오는 다음과 같습니다.

interface IA
{
    void M();
}
interface IB : IA
{
    override void M() { WriteLine("IB"); }
}
class Base : IA
{
    void IA.M() { WriteLine("Base"); }
}
class Derived : Base, IB // allowed?
{
    static void Main()
    {
        IA a = new Derived();
        a.M();           // what does it do?
    }
}

이 동작을 확인하거나 다른 결정을 내려야 합니다.

닫힌 문제: 혼합 클래스 및 인터페이스에 적용되는 가장 특정한 오버라이드 를 위한 초안 제안을 확인합니다(클래스가 인터페이스보다 우선합니다). https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-19.md#diamonds-with-classes참조하십시오.

인터페이스 메소드와 구조체(닫힘)

기본 인터페이스 메서드와 구조체 간에 몇 가지 불행한 상호 작용이 있습니다.

interface IA
{
    public void M() { }
}
struct S : IA
{
}

인터페이스 멤버는 상속되지 않습니다.

var s = default(S);
s.M(); // error: 'S' does not contain a member 'M'

따라서 클라이언트는 인터페이스 메서드를 호출하기 위해 구조체를 상자에 추가해야 합니다.

IA s = default(S); // an S, boxed
s.M(); // ok

이렇게 권투를 하면 struct 유형의 주요 이점이 무효화됩니다. 또한 모든 변이 메서드는 구조체의 상자화된 복사본에 작동하므로 명백한 효과를 가져오지 않습니다.

interface IB
{
    public void Increment() { P += 1; }
    public int P { get; set; }
}
struct T : IB
{
    public int P { get; set; } // auto-property
}

T t = default(T);
Console.WriteLine(t.P); // prints 0
(t as IB).Increment();
Console.WriteLine(t.P); // prints 0

닫힌 문제: 이에 대해 수행할 수 있는 작업:

  1. struct 기본 구현을 상속하는 것을 금지합니다. 모든 인터페이스 메서드는 struct에서 추상 메서드로 처리됩니다. 그런 다음 나중에 더 잘 작동하도록 만드는 방법을 결정하는 데 시간이 걸릴 수 있습니다.
  2. boxing을 방지할 수 있는 코드 생성 전략을 구상합니다. IB.Increment같은 메서드 내에서 this 형식은 IB제한되는 형식 매개 변수와 유사할 수 있습니다. 이와 함께 호출자를 제약하지 않기 위해 비 추상 메서드는 인터페이스에서 상속됩니다. 이렇게 하면 컴파일러 및 CLR 구현 작업이 크게 증가할 수 있습니다.
  3. 그것에 대해 걱정하지 말고 그냥 사마귀로 남겨 둬요.
  4. 다른 아이디어?

결정 : 그것에 대해 걱정하지 않고 그냥 사마귀로 둡니다. https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-19.md#structs-and-default-implementations참조하십시오.

기본 인터페이스 호출(닫힘)

이 결정은 C# 8에서 구현되지 않았습니다. base(Interface).M() 구문은 구현되지 않습니다.

초안 사양은 Java에서 영감을 얻은 기본 인터페이스 호출에 대한 구문을 제안합니다. Interface.base.M(). 적어도 초기 프로토타입에 대한 구문을 선택해야 합니다. 내가 가장 좋아하는 것은 base<Interface>.M().

닫힌 문제: 기본 멤버 호출의 구문은 무엇인가요?

결정: 구문이 base(Interface).M(). https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-19.md#base-invocation참조하십시오. 명명된 인터페이스는 기본 인터페이스여야 하지만 직접 기본 인터페이스일 필요는 없습니다.

오픈 이슈: 클래스 멤버에서 기본 인터페이스 호출을 허용해야 할까요?

결정: 예. https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-19.md#base-invocation

비공용 인터페이스 멤버 재정의(닫힘)

인터페이스에서 기본 인터페이스의 비공용 멤버는 override 한정자를 사용하여 재정의됩니다. 멤버를 포함하는 인터페이스의 이름을 지정하는 "명시적" 재정의인 경우 액세스 한정자는 생략됩니다.

닫힌 문제: 인터페이스 이름을 지정하지 않는 "묵시적" 재정의인 경우 액세스 한정자가 일치해야 하나?

결정: 공용 멤버만 암시적으로 재정의될 수 있으며 액세스 권한이 일치해야 합니다. https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-18.md#dim-implementing-a-non-public-interface-member-not-in-list참조하십시오.

문제 열기:override void IB.M() {}같은 명시적 재정의에서 액세스 한정자가 필요합니까, 선택할 수 있습니까, 아니면 생략되었습니까?

열기 문제:override같은 명시적 재정의에서 void IB.M() {} 필수, 선택 사항 또는 생략인가요?

클래스에서 비공용 인터페이스 멤버를 구현하는 방법은 무엇입니까? 아마도 명시적으로 수행해야 할까요?

interface IA
{
    internal void MI();
    protected void MP();
}
class C : IA
{
    // are these implementations?  Decision: NO
    internal void MI() {}
    protected void MP() {}
}

닫힌 문제: 클래스에서 비공용 인터페이스 멤버를 구현하는 방법은 무엇입니까?

결정: 비공용 인터페이스 멤버만 명시적으로 구현할 수 있습니다. https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-18.md#dim-implementing-a-non-public-interface-member-not-in-list참조하십시오.

의사 결정: 인터페이스 멤버에 override 키워드가 허용되지 않습니다. https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#does-an-override-in-an-interface-introduce-a-new-member

이진 호환성 2(닫힘)

각 형식이 별도의 어셈블리에 있는 다음 코드를 고려합니다.

interface I1
{
    void M() { Impl1 }
}
interface I2 : I1
{
    override void M() { Impl2 }
}
interface I3 : I1
{
}
class C : I2, I3
{
}

우리는 I1.M에서 C의 구현이 I2.M라는 것을 이해합니다. I3 포함하는 어셈블리가 다음과 같이 변경되고 다시 컴파일되면 어떻게 되나요?

interface I3 : I1
{
    override void M() { Impl3 }
}

하지만 C 다시 컴파일되지 않습니다. 프로그램을 실행하면 어떻게 되나요? (C as I1).M() 호출 작업

  1. 실행 I1.M
  2. 실행 I2.M
  3. 실행 I3.M
  4. 2 또는 3, 확정적으로
  5. 일종의 런타임 예외를 발생시킵니다.

의사 결정: 예외를 발생시키다 (5). https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#issues-in-default-interface-methods참조하십시오.

인터페이스에서 partial 허용하시겠습니까? (닫힘)

인터페이스가 추상 클래스를 사용하는 방법과 유사한 방식으로 사용될 수 있다는 점을 감안할 때 partial선언하는 것이 유용할 수 있습니다. 이는 생성기에서 특히 유용합니다.

제안: 인터페이스 및 인터페이스 멤버가 partial선언될 수 없도록 하는 언어 제한을 제거합니다.

결정: 예. https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#permit-partial-in-interface참조하십시오.

인터페이스에서 Main? (닫힘)

열기 문제: 인터페이스 내의 static Main 메서드는 프로그램의 진입점이 될 수 있는 후보입니까?

결정: 예. https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#main-in-an-interface참조하십시오.

공용 비 가상 메서드를 지원하려는 의도 확인(닫힘)

인터페이스에서 가상이 아닌 공용 메서드를 허용하기로 한 결정을 확인(또는 반대)할 수 있나요?

interface IA
{
    public sealed void M() { }
}

Semi-Closed 문제: (2017-04-18) 유용할 것으로 생각하지만 나중에 다시 검토하겠습니다. 이것은 정신 모델의 걸림돌입니다.

결정: 예. https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#confirm-that-we-support-public-non-virtual-methods.

인터페이스의 override이 새로운 멤버를 도입하나요? (닫힘)

재정의 선언에서 새 멤버를 도입하는지 여부를 관찰하는 몇 가지 방법이 있습니다.

interface IA
{
    void M(int x) { }
}
interface IB : IA
{
    override void M(int y) { } // 'override' not permitted
}
interface IC : IB
{
    static void M2()
    {
        M(y: 3); // permitted? Decision: No.
    }
    override void IB.M(int z) { } // permitted? What does it override? Decision: No.
}

오픈 이슈: 인터페이스에서의 재정의 선언이 새 멤버를 도입합니까? (닫힘)

클래스에서 재정의된 메서드는 어떤 면에서 "보이는" 것입니다. 예를 들어 해당 매개 변수의 이름은 재정의된 메서드의 매개 변수 이름보다 우선합니다. 항상 가장 구체적인 오버라이드가 있기 때문에 인터페이스에서 해당 동작을 재현할 수 있습니다. 그러나 우리는 그 행동을 복제하고 싶습니까?

또한 "재정의"된 메서드를 다시 재정의할 수 있나요? [Moot]

의사 결정: 인터페이스 멤버에 override 키워드가 허용되지 않습니다. https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#does-an-override-in-an-interface-introduce-a-new-member.

비공개 접근자가 있는 속성(종료)

프라이빗 멤버는 가상이 아니며 가상과 프라이빗의 조합은 허용되지 않습니다. 비공개 접근자가 있는 속성은 어떻습니까?

interface IA
{
    public virtual int P
    {
        get => 3;
        private set { }
    }
}

허용되는가요? 여기에 set 접근자가 virtual인지 아닌지요? 액세스 가능한 곳에서 재정의할 수 있나요? 다음에서는 get 접근자만 암시적으로 구현하나요?

class C : IA
{
    public int P
    {
        get => 4;
        set { }
    }
}

다음이 오류로 추정되는 이유가 IA.P.set이 가상이 아니며, 또한 접근할 수 없기 때문인가요?

class C : IA
{
    int IA.P
    {
        get => 4;
        set { } // Decision: Not valid
    }
}

의사 결정: 첫 번째 예제는 유효해 보이지만 마지막 예제는 유효하지 않습니다. 이는 C#에서 이미 작동하는 방식과 유사하게 해결됩니다. https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#properties-with-a-private-accessor

기본 인터페이스 호출 작업, 라운드 2 (닫힘)

C# 8에서는 구현되지 않았습니다.

기본 호출을 처리하는 방법에 대한 이전의 "해결책"은 실제로 충분한 표현력을 제공하지 않습니다. C# 및 CLR에서는 Java와 달리 메서드 선언을 포함하는 인터페이스와 호출하려는 구현의 위치를 모두 지정해야 합니다.

인터페이스의 기본 호출에 대해 다음 구문을 제안합니다. 저는 그것을 좋아하지는 않아요, 하지만 그것은 어떤 구문이라도 표현할 수 있어야 하는 것을 보여줍니다.

interface I1 { void M(); }
interface I2 { void M(); }
interface I3 : I1, I2 { void I1.M() { } void I2.M() { } }
interface I4 : I1, I2 { void I1.M() { } void I2.M() { } }
interface I5 : I3, I4
{
    void I1.M()
    {
        base<I3>(I1).M(); // calls I3's implementation of I1.M
        base<I4>(I1).M(); // calls I4's implementation of I1.M
    }
    void I2.M()
    {
        base<I3>(I2).M(); // calls I3's implementation of I2.M
        base<I4>(I2).M(); // calls I4's implementation of I2.M
    }
}

모호성이 없으면 더 간단하게 작성할 수 있습니다.

interface I1 { void M(); }
interface I3 : I1 { void I1.M() { } }
interface I4 : I1 { void I1.M() { } }
interface I5 : I3, I4
{
    void I1.M()
    {
        base<I3>.M(); // calls I3's implementation of I1.M
        base<I4>.M(); // calls I4's implementation of I1.M
    }
}

또는

interface I1 { void M(); }
interface I2 { void M(); }
interface I3 : I1, I2 { void I1.M() { } void I2.M() { } }
interface I5 : I3
{
    void I1.M()
    {
        base(I1).M(); // calls I3's implementation of I1.M
    }
    void I2.M()
    {
        base(I2).M(); // calls I3's implementation of I2.M
    }
}

또는

interface I1 { void M(); }
interface I3 : I1 { void I1.M() { } }
interface I5 : I3
{
    void I1.M()
    {
        base.M(); // calls I3's implementation of I1.M
    }
}

의사 결정: base(N.I1<T>).M(s)결정, 호출 바인딩이 있는 경우 나중에 문제가 발생할 수 있음을 양보했습니다. https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-11-14.md#default-interface-implementations

기본 메서드를 구현하지 않는 구조체에 대한 경고? (닫힘)

@vancem 값 형식 선언이 인터페이스에서 해당 메서드의 구현을 상속하더라도 일부 인터페이스 메서드를 재정의하지 못하는 경우 경고를 생성하는 것을 진지하게 고려해야 한다고 주장합니다. 이는 권투를 유발하고 제한된 호출을 훼손하기 때문입니다.

의사 결정: 분석기에 더 적합해 보입니다. 또한 기본 인터페이스 메서드가 호출되지 않고 boxing이 발생하지 않더라도 발생하므로 이 경고가 시끄러울 수 있는 것처럼 보입니다. https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#warning-for-struct-not-implementing-default-method

인터페이스 정적 생성자(닫힘)

인터페이스 정적 생성자는 언제 실행되나요? 현재 CLI 초안은 첫 번째 정적 메서드 또는 필드에 액세스할 때 발생함을 제안합니다. 그 중 어느 것도 없는 경우 그것은 실행 되지 않을 수 있습니다??

[2018-10-09 CLR 팀은 "valuetypes(각 인스턴스 메서드에 대한 액세스에 대한 cctor 검사)에 대해 수행하는 작업을 미러링할 것"을 제안합니다.

의사 결정: 정적 생성자가 beforefieldinit않은 경우 인스턴스 메서드에 대한 항목에서도 정적 생성자가 실행됩니다. 이 경우 정적 생성자는 첫 번째 정적 필드에 액세스하기 전에 실행됩니다. https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#when-are-interface-static-constructors-run

디자인 회의

2017-03-08 LDM 회의록2017-03-21 LDM 회의록2017-03-23 회의 "기본 인터페이스 메서드에 대한 CLR의 동작"2017-04-05 LDM 회의록2017-04-11 LDM 회의록2017-04-18 LDM 회의록2017-04-19 LDM 회의록2017-05-17 LDM 회의록2017-05-31 LDM 회의록2017-06-14 LDM 회의록2018-10-17 LDM 회의록2018-11-14 LDM 회의록