C # 인터페이스 메서드가 추상 또는 가상으로 선언되지 않은 이유는 무엇입니까?


108

인터페이스의 C # 메서드는 virtual키워드 를 사용하지 않고 선언되고 키워드를 사용하지 않고 파생 클래스에서 재정의됩니다 override.

이것에 대한 이유가 있습니까? 나는 그것이 단지 언어 편의라고 가정하고 분명히 CLR은 이것을 커버 아래에서 처리하는 방법을 알고 있지만 (메소드는 기본적으로 가상이 아님) 다른 기술적 이유가 있습니까?

파생 클래스가 생성하는 IL은 다음과 같습니다.

class Example : IDisposable {
    public void Dispose() { }
}

.method public hidebysig newslot virtual final 
        instance void  Dispose() cil managed
{
  // Code size       2 (0x2)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ret
} // end of method Example::Dispose

메서드는 virtual finalIL에 선언 되어 있습니다.

답변:


145

인터페이스의 경우 추가 abstract또는 public키워드가 중복되므로 생략합니다.

interface MyInterface {
  void Method();
}

CIL에서 메서드는 virtualabstract.

(Java에서는 인터페이스 멤버를 선언 할 수 있습니다 public abstract.)

구현 클래스의 경우 몇 가지 옵션이 있습니다.

재정의 불가능 : C #에서 클래스는 메서드를 virtual. 즉, 파생 클래스에서 재정의 할 수 없습니다 (숨김 만 해당). CIL에서 메서드는 인터페이스 유형과 관련된 다형성을 지원해야하므로 여전히 가상 (그러나 봉인 됨)입니다.

class MyClass : MyInterface {
  public void Method() {}
}

재정의 가능 : C # 및 CIL 모두에서 메서드는 virtual. 다형성 디스패치에 참여하고 재정의 할 수 있습니다.

class MyClass : MyInterface {
  public virtual void Method() {}
}

명시 적 : 이것은 클래스가 인터페이스를 구현하지만 클래스 자체의 공용 인터페이스에 인터페이스 메서드를 제공하지 않는 방법입니다. CIL에서 메서드는 private(!)이지만 해당 인터페이스 유형에 대한 참조에서 클래스 외부에서 여전히 호출 할 수 있습니다. 명시 적 구현도 재정의 할 수 없습니다. 이는 .overrideprivate 메서드를 구현중인 해당 인터페이스 메서드에 연결 하는 CIL 지시문 ( )이 있기 때문에 가능합니다 .

[씨#]

class MyClass : MyInterface {
  void MyInterface.Method() {}
}

[CIL]

.method private hidebysig newslot virtual final instance void MyInterface.Method() cil managed
{
  .override MyInterface::Method
}

VB.NET에서는 구현 클래스에서 인터페이스 메서드 이름을 별칭으로 지정할 수도 있습니다.

[VB.NET]

Public Class MyClass
  Implements MyInterface
  Public Sub AliasedMethod() Implements MyInterface.Method
  End Sub
End Class

[CIL]

.method public newslot virtual final instance void AliasedMethod() cil managed
{
  .override MyInterface::Method
}

이제이 이상한 경우를 고려하십시오.

interface MyInterface {
  void Method();
}
class Base {
  public void Method();
}
class Derived : Base, MyInterface { }

동일한 어셈블리에서 BaseDerived이 선언 된 경우 컴파일러는 인터페이스를 구현하지 Base::Method않더라도 가상 및 봉인 (CIL에서)을 Base만듭니다.

경우 BaseDerived다른 어셈블리에있는을 컴파일 할 때, Derived조립이에 멤버를 소개합니다, 그래서 컴파일러는, 다른 어셈블리를 변경하지 않습니다 Derived에 대한 명시 적으로 구현 될 것 MyInterface::Method그건 그냥 호출을 위임합니다 Base::Method.

따라서 모든 인터페이스 메소드 구현은 다형성 동작을 지원해야하므로 컴파일러가이를 수행하기 위해 후프를 거쳐야하는 경우에도 CIL에서 가상으로 표시되어야합니다.


73

여기 CSharp 3rd Edition을 통해 CLR에서 Jeffrey Ritcher를 인용

CLR을 사용하려면 인터페이스 메서드가 가상으로 표시되어야합니다. 소스 코드에서 메서드를 가상으로 명시 적으로 표시하지 않으면 컴파일러는 메서드를 가상 및 봉인 된 것으로 표시합니다. 이렇게하면 파생 클래스가 인터페이스 메서드를 재정의하지 못합니다. 메서드를 명시 적으로 가상으로 표시하면 컴파일러는 메서드를 가상으로 표시하고 봉인되지 않은 상태로 둡니다. 이렇게하면 파생 클래스가 인터페이스 메서드를 재정의 할 수 있습니다. 인터페이스 메서드가 봉인 된 경우 파생 클래스가 메서드를 재정의 할 수 없습니다. 그러나 파생 클래스는 동일한 인터페이스를 다시 상속 할 수 있으며 인터페이스의 메서드에 대한 자체 구현을 제공 할 수 있습니다.


25
인용문에는 인터페이스 메소드 구현이 가상으로 표시되어야하는 이유 가 나와 있지 않습니다 . 인터페이스 유형과 관련하여 다형성이기 때문에 가상 메소드 디스패치를 ​​허용 하려면 가상 테이블 에 슬롯 이 필요 합니다 .
Jordão

1
인터페이스 메서드를 가상으로 명시 적으로 표시 할 수 없으며 "오류 CS0106 : 수정 자 '가상'이이 항목에 유효하지 않습니다."라는 오류가 발생합니다. v2.0.50727 (내 PC에서 가장 오래된 버전)을 사용하여 테스트했습니다.
ccppjava

3
@ccppjava 아래의 Jorado의 설명에서 가상 인터페이스를 구현하는 클래스 멤버를 표시하여 하위 클래스가 클래스를 재정의 할 수 있도록합니다.
Christopher Stevenson

13

예, 인터페이스 구현 방법은 런타임과 관련하여 가상입니다. 구현 세부 사항이며 인터페이스를 작동시킵니다. 가상 메서드는 클래스의 v- 테이블에서 슬롯을 가져오고 각 슬롯에는 가상 메서드 중 하나에 대한 포인터가 있습니다. 개체를 인터페이스 유형으로 캐스팅하면 인터페이스 메서드를 구현하는 테이블 섹션에 대한 포인터가 생성됩니다. 인터페이스 참조를 사용하는 클라이언트 코드는 이제 인터페이스 포인터 등의 오프셋 0에서 첫 번째 인터페이스 메서드 포인터를 봅니다.

원래 답변에서 과소 평가 한 것은 최종 속성 의 중요성입니다 . 파생 클래스가 가상 메서드를 재정의하는 것을 방지합니다. 파생 클래스는 인터페이스를 다시 구현해야합니다, 구현 방법은 그림자 기본 클래스 메서드를. 구현 방법이 가상이 아니라는 C # 언어 계약을 구현하기에 충분합니다.

Example 클래스의 Dispose () 메서드를 virtual로 선언하면 최종 속성이 제거 되는 것을 볼 수 있습니다. 이제 파생 클래스가이를 재정의 할 수 있습니다.


4

대부분의 다른 컴파일 된 코드 환경에서 인터페이스는 메서드 본문에 대한 포인터 목록 인 vtables로 구현됩니다. 일반적으로 여러 인터페이스를 구현하는 클래스는 내부 컴파일러 생성 메타 데이터의 어딘가에 인터페이스 vtable 목록 (메서드 순서가 유지되도록 인터페이스 당 하나의 vtable)이 있습니다. 이것이 COM 인터페이스가 일반적으로 구현되는 방법이기도합니다.

그러나 .NET에서는 인터페이스가 각 클래스에 대해 별개의 vtable로 구현되지 않습니다. 인터페이스 메소드는 모든 인터페이스가 속한 전역 인터페이스 메소드 테이블을 통해 인덱싱됩니다. 따라서 해당 메서드가 인터페이스 메서드를 구현하기 위해 가상 메서드를 선언 할 필요가 없습니다. 전역 인터페이스 메서드 테이블은 클래스 메서드의 코드 주소를 직접 가리킬 수 있습니다.

인터페이스를 구현하기 위해 가상 메서드를 선언하는 것은 비 CLR 플랫폼에서도 다른 언어에서도 필요하지 않습니다. Win32의 델파이 언어가 한 가지 예입니다.


0

그들은 가상이 아닙니다 (기본 구현 측면에서가 아니라면 (밀봉 된 가상)-여기에서 다른 답변을 읽고 직접 배우는 것이 좋습니다 :-)

그들은 아무것도 재정의하지 않습니다-인터페이스에 구현이 없습니다.

인터페이스가 수행하는 모든 작업은 클래스가 준수해야하는 "계약"(원하는 경우 패턴)을 제공하여 호출자가 이전에 특정 클래스를 본 적이없는 경우에도 객체를 호출하는 방법을 알 수 있도록합니다.

그런 다음 계약의 범위 내에서 가상 또는 "비가 상"(바로 봉인 된 가상) 내에서 인터페이스 메서드를 구현하는 것은 클래스에 달려 있습니다.


이 스레드의 모든 사람은 인터페이스가 무엇인지 알고 있습니다. 문제는 매우 다릅니다 - 생성 된 IL은 이다 인터페이스 방법에 대한 가상 및 비 인터페이스 방법에 대한 가상 없습니다.
Rex M

5
예, 질문이 수정 된 후 답변을 비판하는 것은 정말 쉽습니다.
Jason Williams

0

인터페이스는 구현 인터페이스, 당신은 단지 클래스는 인터페이스에서이 특별한 방법이 있어야합니다 "라고하는 클래스를 선언 할 때, 클래스보다 더 추상적 인 개념이며, 처리 여부 문제가되지 않는 정적 , 가상 , 비 가상 , 오버라이드 (override) 로 동일한 ID와 동일한 유형 매개 변수가있는 경우 ".

Object Pascal ( "Delphi") 및 Objective-C (Mac)와 같은 인터페이스를 지원하는 다른 언어에서는 인터페이스 메서드를 가상이 아닌 가상으로 표시 할 필요가 없습니다.

그러나 당신이 옳을 수 있습니다 . 특정 인터페이스를 구현하는 클래스 메소드 를 제한 하려는 경우 인터페이스에 특정 "가상"/ "재정의"속성을 갖는 것이 좋은 생각 일 수 있습니다 . 그러나 이는 또한 두 인터페이스 모두에 대해 "nonvirtual", "dontcareifvirtualornot"키워드가 있음을 의미합니다.

클래스 메소드가 "@virtual"또는 "@override"를 사용하여 메소드가 가상인지 확인해야 할 때 Java에서 비슷한 것을 보았 기 때문에 귀하의 질문을 이해합니다.


@override는 실제로 코드의 동작을 변경하거나 결과 바이트 코드를 변경하지 않습니다. 이것이하는 일은 컴파일러에게 그렇게 데코 레이팅 된 메서드가 오버라이드로 의도 된 것임을 알리는 것입니다. 이것은 컴파일러가 온 전성 검사를 할 수 있도록합니다. C #은 다르게 작동합니다. override언어 자체에서 일류 키워드입니다.
Robert Harvey
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.