C #에서 기본적으로 비가 상 메서드를 구현하는 이유는 무엇입니까?


105

Java와 달리 C #은 기본적으로 메서드를 비가 상 함수로 취급하는 이유는 무엇입니까? 다른 가능한 결과보다 성능 문제 일 가능성이 더 높습니까?

Anders Hejlsberg의 한 단락을 읽고 기존 아키텍처가 가져다주는 몇 가지 이점에 대해 생각했습니다. 그러나 부작용은 어떻습니까? 기본적으로 가상이 아닌 방법을 사용하는 것이 정말 좋은 절충안입니까?


1
성능상의 이유로 언급 대답은 C # 컴파일러는 대부분에 메소드 호출 컴파일 있다는 사실 간과 callvirt를 하지 전화를 . 이것이 C #에서 this참조가 null 인 경우 다르게 동작하는 메서드를 가질 수없는 이유입니다 . 자세한 정보는 여기 를 참조 하십시오 .
Andy

진실! call IL 명령어는 대부분 정적 메서드에 대한 호출을위한 것입니다.
RBT

1
C #의 건축가 Anders Hejlsberg의 생각은 여기여기 입니다.
RBT

답변:


98

상속을 이용할 수 있도록 클래스를 설계 해야 합니다. virtual기본적으로 메서드 가 있다는 것은 클래스의 모든 함수를 플러그 아웃하고 다른 함수로 대체 할 수 있음을 의미합니다. 이는 실제로 좋지 않습니다. 많은 사람들은 수업이 sealed기본적으로 이루어져야한다고 생각합니다 .

virtual메서드는 성능에 약간의 영향을 미칠 수도 있습니다. 그러나 이것이 주된 이유는 아닐 것입니다.


7
개인적으로 나는 성능에 대한 부분을 의심합니다. 가상 함수의 경우에도 컴파일러는 IL 코드에서 callvirt단순하게 대체 할 위치를 파악할 수 call있습니다 (또는 JITting의 경우 더 많은 다운 스트림). Java HotSpot도 마찬가지입니다. 나머지 답변은 정확합니다.
Konrad Rudolph

5
나는 성능이 선두에 있지 동의하지만, 그것은 할 수 도 성능에 영향이있다. 아마도 아주 작을 것입니다. 그러나 이것이이 선택 의 이유 는 아닙니다 . 그것을 언급하고 싶었습니다.
Mehrdad Afshari

71
봉인 된 수업은 내가 아기를 때리고 싶게 만듭니다.
mxmissile

6
@mxmissile : 나도 그래도 좋은 API 디자인은 어렵습니다. 기본적으로 봉인 된 것이 당신이 소유 한 코드에 완벽하게 합리적이라고 생각합니다. 종종 팀의 다른 프로그래머는 필요한 클래스를 구현할 때 상속을 고려 하지 않았으며 기본적으로 봉인되어 해당 메시지를 매우 잘 전달합니다.
Roman Starkov

49
많은 사람들도 사이코 패스이며 우리가 그들의 말을 들어야한다는 의미는 아닙니다. Virtual은 C #의 기본값이어야하며 코드는 구현을 변경하거나 완전히 다시 작성할 필요없이 확장 가능해야합니다. API를 과도하게 보호하는 사람들은 종종 다른 사람이이를 오용하는 시나리오보다 훨씬 더 나쁜 API를 사용하지 않거나 절반 만 사용하게됩니다.
Chris Nicola

89

기본적으로 비가 상이 아닌 것이 작업을 수행하는 올바른 방법이라는 합의가 여기에있는 것 같습니다. 나는 다른 쪽에서 내려갈 것입니다. – 저는 실용적이라고 생각합니다 – 울타리의 측면.

대부분의 정당화는 "우리가 당신에게 힘을 주면 당신이 스스로를 해칠 수있다"는 주장처럼 나에게 읽혀진다. 프로그래머로부터?!

상속 및 / 또는 확장 성을 위해 라이브러리를 설계하기에 충분하지 않은 (또는 충분한 시간이있는) 코더가 내가 수정하거나 조정해야 할 라이브러리를 정확히 생성 한 코더 인 것처럼 보입니다. 재정의 기능이 가장 유용하게 사용되는 라이브러리.

추악하고 절박한 해결 방법 코드를 작성해야하는 횟수 (또는 사용을 포기하고 대체 솔루션을 롤링하기 위해)해야하는 횟수는 내가 물린 횟수보다 훨씬 더 큽니다. 예를 들어 Java에서) 디자이너가 내가 생각하지 않았을 수도있는 곳을 재정의함으로써.

기본적으로 가상이 아닌 것은 내 삶을 더 어렵게 만듭니다.

업데이트 : 내가 실제로 질문에 대답하지 않았다는 것이 [정확히] 지적되었습니다. 그래서-그리고 다소 늦게 된 것에 대해 사과합니다 ....

저는 "C #은 프로그래머보다 프로그램을 더 중요시하는 잘못된 결정을 내렸기 때문에 기본적으로 비가 상 메서드를 구현하는 C #"과 같은 간결한 것을 작성할 수 있기를 원했습니다. (성능 (조기 최적화, 누구?) 또는 클래스 동작 보장과 같은이 질문에 대한 다른 답변을 기반으로 어느 정도 정당화 될 수 있다고 생각합니다.)

그러나 나는 Stack Overflow가 원하는 결정적인 대답이 아니라 내 의견을 말하고 있다는 것을 알고 있습니다. 확실히, 최고 수준에서 결정적인 (그러나 도움이되지 않는) 대답은 다음과 같다고 생각했습니다.

언어 설계자가 결정을 내렸고 그것이 그들이 선택한 것이기 때문에 기본적으로 가상이 아닙니다.

이제 나는 그들이 그 결정을 내린 정확한 이유를 우리는 결코 .... 아, 기다려! 대화의 대본!

따라서 API 재정의의 위험성과 상속을 위해 명시 적으로 설계해야하는 필요성에 대한 답변과 의견이 올바른 방향으로 진행되고 있지만 중요한 시간적 측면이 누락 된 것 같습니다. Anders의 주요 관심사는 클래스 또는 API의 암시 적 유지에 대한 것이 었습니다. 버전 간 계약 . 그리고 그는 실제로 플랫폼 위에서 사용자 코드 변경에 대해 걱정하기보다는 .Net / C # 플랫폼이 코드 아래에서 변경되도록 허용하는 것에 대해 더 걱정한다고 생각합니다. (그리고 그의 "실용적인"관점은 그가 다른 쪽에서 바라보고 있기 때문에 나의 것과 정반대입니다.)

(그러나 그들은 기본적으로 가상을 선택하고 코드베이스를 통해 "최종"을 뽑아 낼 수 없었습니까? 아마도 그것은 똑같지 않을 것입니다 .. Anders는 분명히 저보다 똑똑하기 때문에 거짓말을하겠습니다.)


2
더 이상 동의 할 수 없습니다. 타사 API를 사용하고 일부 동작을 재정의하고 싶지만 할 수없는 경우 매우 실망 스럽습니다.
Andy

3
나는 열정적으로 동의합니다. API를 게시하려는 경우 (회사 내부 또는 외부 세계에 관계없이) 코드가 상속을 위해 설계되었는지 확인해야합니다. 많은 사람들이 사용할 수 있도록 게시하는 경우 API를 훌륭하고 세련되게 만들어야합니다. 좋은 전체 디자인 (좋은 콘텐츠, 명확한 사용 사례, 테스트, 문서)에 필요한 노력에 비해 상속을위한 디자인은 그리 나쁘지 않습니다. 게시하지 않는 경우 기본적으로 가상 가상은 시간이 덜 걸리며 문제가있는 경우 항상 작은 부분을 수정할 수 있습니다.
Gravity

2
이제 Visual Studio에 모든 메서드 / 속성에 자동으로 태그를 지정하는 편집기 기능 만 있다면 virtual... Visual Studio 추가 기능 누구?
케빈 아 르페

1
나는 전적으로 당신에게 동의하지만 이것은 정확히 대답이 아닙니다.
크리스 모건

2
의존성 주입, 모의 프레임 워크, ORM과 같은 현대적인 개발 관행을 고려할 때, 우리의 C # 디자이너가 그 기준을 약간 놓친 것이 분명해 보입니다. 기본적으로 속성을 재정의 할 수없는 경우 종속성을 테스트해야하는 것은 매우 실망 스럽습니다.
Jeremy Holovacs 2013

17

메서드가 재정의되고이를위한 디자인이 아니라는 사실을 잊기 쉽기 때문입니다. C #은 가상으로 만들기 전에 생각하게합니다. 이것은 훌륭한 디자인 결정이라고 생각합니다. 일부 사람들 (예 : Jon Skeet)은 클래스가 기본적으로 봉인되어야한다고 말했습니다.


12

다른 사람들의 말을 요약하면 몇 가지 이유가 있습니다.

1- C #에는 C ++에서 직접 나오는 구문 및 의미 체계가 많이 있습니다. C ++에서 기본적으로 가상이 아닌 메서드가 C #에 영향을 미쳤다는 사실.

2- 모든 메서드 호출은 개체의 가상 테이블을 사용해야하므로 기본적으로 모든 메서드를 가상으로 설정하는 것은 성능 문제입니다. 또한 이는 메서드를 인라인하고 다른 종류의 최적화를 수행하는 Just-In-Time 컴파일러의 기능을 강력하게 제한합니다.

3- 가장 중요한 것은 메서드가 기본적으로 가상이 아닌 경우 클래스의 동작을 보장 할 수 있다는 것입니다. Java와 같이 기본적으로 가상 인 경우 간단한 getter 메서드가 의도 한대로 수행된다는 것을 보장 할 수 없습니다. 방법 및 / 또는 클래스 최종).

Zifre가 언급했듯이 C # 언어가 한 단계 더 나아가 기본적으로 클래스를 봉인하지 않은 이유가 궁금 할 수 있습니다. 이것은 구현 상속 문제에 대한 전체 토론의 일부이며 매우 흥미로운 주제입니다.


1
기본적으로 선호하는 클래스도 봉인되었을 것입니다. 그러면 일관성이 있습니다.
Andy

9

C #은 C ++의 영향을받습니다. C ++는 기본적으로 동적 디스패치 (가상 함수)를 활성화하지 않습니다. 이에 대한 한 가지 (좋은?) 주장은 "클래스 계층의 구성원 인 클래스를 얼마나 자주 구현합니까?"라는 질문입니다. 기본적으로 동적 디스패치를 ​​사용하지 않는 또 다른 이유는 메모리 공간입니다. 가상 테이블을 가리키는 가상 포인터 (vpointer)가없는 클래스 는 물론 후기 바인딩이 활성화 된 해당 클래스보다 작습니다.

성능 문제는 "예"또는 "아니오"라고 말하기가 쉽지 않습니다. 그 이유는 C #의 런타임 최적화 인 JIT (Just In Time) 컴파일 때문입니다.

" 가상 통화 속도 .. "에 대한 또 다른 유사한 질문


JIT 컴파일러로 인해 가상 메서드가 C #에 성능에 영향을 미치는지 의심 스럽습니다. JIT가 오프라인 컴파일보다 더 나은 영역 중 하나입니다. 런타임 전에 "알 수없는"함수 호출을 인라인 할 수 있기 때문입니다
David Cournapeau

1
사실, 기본적으로 그렇게하는 C ++보다 Java의 영향을 더 많이받는다고 생각합니다.
Mehrdad Afshari

5

단순한 이유는 성능 비용 외에도 설계 및 유지 보수 비용입니다. 가상 메서드는 비가 상 메서드에 비해 추가 비용이 발생합니다. 클래스 디자이너는 메서드가 다른 클래스에 의해 재정의 될 때 발생하는 작업을 계획해야하기 때문입니다. 특정 메서드가 내부 상태를 업데이트하거나 특정 동작이있을 것으로 예상하는 경우 이는 큰 영향을 미칩니다. 이제 파생 클래스가 해당 동작을 변경할 때 어떤 일이 발생하는지 계획해야합니다. 그런 상황에서 신뢰할 수있는 코드를 작성하는 것이 훨씬 더 어렵습니다.

비가 상 방법을 사용하면 완벽하게 제어 할 수 있습니다. 잘못되는 것은 원저자의 잘못입니다. 코드는 추론하기가 훨씬 쉽습니다.


이것은 정말 오래된 게시물이지만 첫 번째 프로젝트를 작성하는 초보자에게는 내가 작성한 코드의 의도하지 않은 / 알 수없는 결과에 대해 끊임없이 불안해합니다. 가상이 아닌 방법이 전적으로 내 잘못이라는 것을 알면 매우 편안합니다.
trevorc

2

모든 C # 메서드가 가상이면 vtbl이 훨씬 더 커집니다.

C # 개체에는 클래스에 가상 메서드가 정의되어있는 경우에만 가상 메서드가 있습니다. 모든 개체에 vtbl에 해당하는 형식 정보가 포함되어 있지만 가상 메서드가 정의되지 않은 경우 기본 개체 메서드 만 존재합니다.

@Tom Hawtin : C ++, C # 및 Java가 모두 C 언어 계열이라고 말하는 것이 더 정확할 것입니다. :)


1
vtable이 훨씬 더 커지는 것이 왜 문제일까요? vtable은 클래스 당 (인스턴스 당이 아님) 1 개만 있으므로 크기가 큰 차이를 만들지는 않습니다.
Gravity

1

Perl 배경에서 왔기 때문에 C #은 새로운 클래스의 모든 사용자가 잠재적으로 배후에서 인식하도록 강요하지 않고 비가 상 메서드를 통해 기본 클래스의 동작을 확장하고 수정하려는 모든 개발자의 운명을 봉인했다고 생각합니다. 세부.

List 클래스의 Add 메서드를 고려하십시오. 특정 목록이 '추가'될 때마다 개발자가 여러 잠재적 데이터베이스 중 하나를 업데이트하려는 경우 어떻게해야합니까? 'Add'가 기본적으로 가상 인 경우 개발자는 모든 클라이언트 코드가 일반 'List'대신 'BackedList'라는 것을 알도록 강요하지 않고 'Add'메서드를 덮어 쓰는 'BackedList'클래스를 개발할 수 있습니다. 모든 실용적인 목적을 위해 'BackedList'는 클라이언트 코드에서 다른 'List'로 볼 수 있습니다.

이는 데이터베이스에있는 하나 이상의 스키마에 의해 자체적으로 지원되는 하나 이상의 목록 구성 요소에 대한 액세스를 제공 할 수있는 대규모 기본 클래스의 관점에서 의미가 있습니다. C # 메서드는 기본적으로 가상이 아니므로 기본 클래스에서 제공하는 목록은 간단한 IEnumerable 또는 ICollection 또는 List 인스턴스 일 수 없지만 대신 새 버전을 보장하기 위해 클라이언트에 'BackedList'로 알려야합니다. 올바른 스키마를 업데이트하기 위해 '추가'작업이 호출됩니다.


사실, 가상 방법은 기본적으로 작업을 더 쉽게 만들어 주지만 말이됩니까? 삶을 더 쉽게 만들기 위해 사용할 수있는 것들이 많이 있습니다. 클래스의 공개 필드가 아닌 이유는 무엇입니까? 행동을 바꾸는 것은 너무 쉽습니다. 제 생각에는 언어의 모든 것은 엄격하게 캡슐화되고, 엄격하며, 기본적으로 변경에 탄력적이어야합니다. 필요한 경우에만 변경하십시오. 단지 철학적 인 abt 디자인 결정일뿐입니다. 계속 ..
nawfal

... 현재 주제에 대해 이야기하려면, 상속 모델은 경우에만 사용해야 B입니다 A. 경우 B에서 뭔가 다른이 필요 A는 없습니다 다음 A. 언어 자체의 기능으로서 재정의하는 것이 디자인 결함이라고 생각합니다. 다른 Add메서드 가 필요한 경우 컬렉션 클래스는 List. 그것을 말하려는 것은 가짜입니다. 여기서 올바른 접근 방식은 구성입니다 (위조가 아님). 사실 전체 프레임 워크는 오버라이드 기능에 기반을두고 있지만, 저는 그저 마음에 들지 않습니다.
nawfal 2013 년

나는 당신의 요점을 이해한다고 생각하지만 제공된 구체적인 예에서 'BackedList'는 'IList'인터페이스를 구현할 수 있으며 클라이언트는 인터페이스에 대해서만 알고 있습니다. 권리? 내가 뭘 놓치고 있니? 그러나 나는 당신이하려는 더 넓은 요점을 이해합니다.
Vetras

0

확실히 성능 문제는 아닙니다. Sun의 Java 인터프리터는 동일한 코드를 사용하여 디스패치 ( invokevirtual바이트 코드)하고 HotSpot은 여부에 관계없이 정확히 동일한 코드를 생성합니다 final. 모든 C # 개체 (구조체 제외)에는 가상 메서드가 있으므로 항상 vtbl/ runtime 클래스 식별이 필요합니다 . C #은 "자바와 유사한 언어"의 방언입니다. 그것이 C ++에서 나온 것이라고 제안하는 것은 전적으로 정직하지 않습니다.

"상속을 위해 디자인하지 않으면 금지"해야한다는 생각이 있습니다. 빠른 수정이 필요한 심각한 비즈니스 케이스가있는 순간까지 좋은 아이디어처럼 들립니다. 제어 할 수없는 코드에서 상속 할 수 있습니다.


모든 방법이 기본적으로 가상이기 때문에 성능에 큰 영향을 미치기 때문에 핫스팟은 최적화를 수행하기 위해 엄청난 시간을 투자해야합니다. CoreCLR는 훨씬 간단되는 동안 유사한 성능을 달성 할 수있다
Yair Halberstadt

@YairHalberstadt Hotspot은 다양한 다른 이유로 컴파일 된 코드를 제거 할 수 있어야합니다. 소스를 살펴본 지 몇 년이 지났지 만 final효과적인 final방법 의 차이 는 사소합니다. 두 가지 다른 구현을 가진 인라인 메서드 인 바이 모픽 인라인을 수행 할 수 있다는 점도 주목할 가치가 있습니다.
Tom Hawtin-tackline
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.