C #이 이런 식으로 한 이유를 물었으므로 C # 작성자에게 문의하는 것이 가장 좋습니다. C #의 수석 아키텍트 인 Anders Hejlsberg는 인터뷰 에서 기본적으로 (Java와 같이) 가상으로 가지 않기로 선택한 이유를 다음과 같이 대답했습니다 .
Java는 기본적으로 메소드를 비가 상으로 표시하기 위해 final 키워드 와 함께 가상을가집니다. 여전히 두 가지 개념을 배우지 만 많은 사람들은 최종 키워드에 대해 알지 못하거나 적극적으로 사용하지 않습니다. C #은 가상 및 새 / 재정의를 사용하여 의도적으로 이러한 결정을 내립니다.
몇 가지 이유가 있습니다. 하나는 성능 입니다. 사람들이 Java로 코드를 작성할 때 메소드를 최종으로 표시하는 것을 잊어 버렸습니다. 따라서 이러한 방법은 가상입니다. 그것들은 가상이기 때문에 성능이 좋지 않습니다. 가상 메소드와 관련된 성능 오버 헤드가 있습니다. 그것은 하나의 문제입니다.
더 중요한 문제는 버전 관리 입니다. 가상 방법에 대한 두 가지 생각 학교가 있습니다. 아카데믹 스쿨은 "언젠가 그것을 무시하고 싶을 수 있기 때문에 모든 것이 가상이어야한다"고 말합니다. 실제 환경에서 실행되는 실제 응용 프로그램을 구축하여 나온 실용적인 사고 학교는 "우리는 가상으로 만드는 것에 대해 신중해야합니다."라고 말합니다.
우리가 플랫폼에서 가상의 것을 만들 때, 우리는 그것이 미래에 어떻게 진화하는지에 대해 엄청나게 많은 약속을합니다. 비가 상 메서드의 경우이 메서드를 호출하면 x와 y가 발생합니다. API에서 가상 메서드를 게시 할 때이 메서드를 호출 할 때 x와 y가 발생한다고 약속 할뿐입니다. 또한이 메소드를 재정의하면 다른 메소드와 관련하여이 특정 순서로 메소드를 호출하고 상태는이 상태와 불변 상태가됩니다.
API에서 virtual을 말할 때마다 콜백 후크가 생성됩니다. OS 또는 API 프레임 워크 디자이너로서 이에 대해주의를 기울여야합니다. 이러한 약속을 반드시 이행 할 수는 없기 때문에 사용자가 API의 임의의 시점에서 사용자를 재정의하고 후킹하는 것을 원하지 않습니다. 그리고 사람들은 무언가를 가상으로 만들 때의 약속을 완전히 이해하지 못할 수도 있습니다.
인터뷰는 개발자가 클래스 상속 디자인에 대해 어떻게 생각하는지와 그것이 어떻게 결정을 이끌어 냈는지에 대해 더 많은 토론을했습니다.
이제 다음 질문으로 넘어갑니다.
나는 왜 세상에서 BaseClass와 동일한 이름과 동일한 서명으로 DerivedClass에 메소드를 추가하고 새로운 동작을 정의 할 것인지 이해할 수 없지만 런타임 다형성에서 BaseClass 메소드가 호출됩니다! (이것은 재정의되지 않고 논리적으로 있어야합니다).
파생 클래스가 기본 클래스의 계약을 준수하지 않지만 이름이 같은 메서드가 있음을 선언하려는 경우입니다. C # new
과 override
C # 의 차이점을 모르는 사람 은이 MSDN 페이지를 참조하십시오 .
매우 실용적인 시나리오는 다음과 같습니다.
- 라는 클래스가있는 API를 만들었습니다
Vehicle
.
- API를 사용하기 시작했고 파생되었습니다
Vehicle
.
- 귀하의
Vehicle
클래스는 어떤 방법이 없었다 PerformEngineCheck()
.
- 내에서
Car
클래스 나 메소드를 추가 PerformEngineCheck()
.
- 새 버전의 API를 출시하고를 추가했습니다
PerformEngineCheck()
.
- 클라이언트가 API에 의존하기 때문에 메소드의 이름을 바꿀 수 없어서 메소드가 손상 될 수 있습니다.
따라서 새 API를 다시 컴파일하면 C #에서이 문제에 대해 경고합니다.
베이스 PerformEngineCheck()
가 아닌 경우 virtual
:
app2.cs(15,17): warning CS0108: 'Car.PerformEngineCheck()' hides inherited member 'Vehicle.PerformEngineCheck()'.
Use the new keyword if hiding was intended.
그리고 기지 PerformEngineCheck()
가 virtual
:
app2.cs(15,17): warning CS0114: 'Car.PerformEngineCheck()' hides inherited member 'Vehicle.PerformEngineCheck()'.
To make the current member override that implementation, add the override keyword. Otherwise add the new keyword.
이제 클래스가 실제로 기본 클래스의 계약을 확장하는지 아니면 다른 계약이지만 동일한 이름인지 여부를 명시 적으로 결정해야합니다.
- 를함으로써
new
기본 방법의 기능이 파생 된 방법과 다른 있었다면, 나는 내 고객을 아프게하지 않습니다. 참조 된 코드는 호출 Vehicle
되지 않지만 참조 Car.PerformEngineCheck()
가있는 코드 Car
는에서 제공 한 것과 동일한 기능을 계속 볼 수 PerformEngineCheck()
있습니다.
비슷한 예는 기본 클래스의 다른 메소드가 호출 할 수 PerformEngineCheck()
있는 경우 (예 : 최신 버전) PerformEngineCheck()
파생 클래스의 메소드 를 호출하지 못하게하는 방법은 무엇입니까? Java에서는 해당 결정이 기본 클래스와 관련이 있지만 파생 클래스에 대해서는 아무것도 모릅니다. C #에서, 그 결정이 달려 모두 (비아 기본 클래스에 대한 virtual
키워드를) 및 파생 클래스 (바이어 new
및 override
키워드).
물론, 컴파일러에서 발생하는 오류는 프로그래머가 예기치 않게 오류를 발생시키지 않도록하는 유용한 도구를 제공합니다 (예 :이를 무시하지 않고 새로운 기능을 무시하거나 제공함).
Anders가 말했듯이, 현실 세계는 우리를 처음부터 시작한다면 결코 들어가고 싶지 않은 그런 문제로 우리를 강요합니다.
편집 : new
인터페이스 호환성을 보장하기 위해 사용해야하는 위치에 대한 예가 추가되었습니다 .
편집 : 의견을 검토하는 동안 Eric Lippert (C # 디자인위원회 회원 중 하나)가 다른 예제 시나리오 (Brian이 언급 한)에 대한 글을 읽었습니다.
2 부 : 업데이트 된 질문에 근거
그러나 C #의 경우 SpaceShip이 Vehicle 클래스의 가속을 재정의하지 않고 new를 사용하면 코드의 논리가 손상됩니다. 단점이 아닌가?
누가 SpaceShip
실제로 재정의 할지 아니면 다른지를 결정 Vehicle.accelerate()
합니까? SpaceShip
개발자 여야합니다 . 따라서 SpaceShip
개발자가 기본 클래스의 계약을 유지하지 않기로 결정한 경우으로 전화를 Vehicle.accelerate()
걸어야 SpaceShip.accelerate()
합니까? 그때는로 표시됩니다 new
. 그러나 계약을 실제로 유지하기로 결정한 경우 실제로 계약을 표시합니다 override
. 두 경우 모두 계약 에 따라 올바른 메소드를 호출하여 코드가 올바르게 작동합니다 . 코드 SpaceShip.accelerate()
가 실제로 재정의 하는지 Vehicle.accelerate()
또는 이름 충돌 인지 어떻게 결정할 수 있습니까? (위의 예를 참조하십시오).
그러나 암시 적 상속의 경우 계약 을 SpaceShip.accelerate()
유지하지 않더라도 메소드 호출은 여전히로 이동합니다 .Vehicle.accelerate()
SpaceShip.accelerate()