새 키워드가 필요한 이유는 무엇이며 기본 동작을 숨기고 재정의하지 않는 이유는 무엇입니까?


81

블로그 게시물 을보고 다음과 같은 질문이있었습니다.

  • new키워드 가 필요한 이유는 기본 클래스 메서드가 숨겨 지도록 지정하는 것입니다. 내 말은, 우리는 왜 그것을 필요로합니까? override키워드를 사용하지 않으면 기본 클래스 메서드를 숨기지 않습니까?
  • C #의 기본값이 숨기고 재정의하지 않는 이유는 무엇입니까? 디자이너가이 방식으로 구현 한 이유는 무엇입니까?

6
(나에게) 조금 더 흥미로운 질문은 왜 숨는 것이 합법적인지입니다. 일반적으로 오류 (따라서 컴파일러 경고)이며, 거의 원하지 않고 항상 문제가 있습니다.
Konrad Rudolph

답변:


127

좋은 질문입니다. 다시 말하겠습니다.

다른 방법으로 방법을 숨기는 것이 합법적 인 이유는 무엇입니까?

예를 들어 그 질문에 답하겠습니다. CLR v1의 인터페이스가 있습니다.

interface IEnumerable
{
    IEnumerator GetEnumerator();
}

감독자. 이제 CLR에서 당신은 제네릭이 있고 V1 만 우리가 가진 거라고 제네릭 내가이 일반적인 인터페이스를 만들었을 것입니다 경우 "사람이.하지만. 내가 지금과 호환 뭔가해야하지 않았다고 생각 V2 이다 일반적인 그래서를 IEnumerable을 기대하는 코드와의 하위 호환성을 잃지 않고 제네릭의 이점을 얻었습니다. "

interface IEnumerable<T> : IEnumerable
{
    IEnumerator<T> .... uh oh

무엇의 GetEnumerator 메서드를 호출 할 예정 IEnumerable<T>입니까? 당신이 기억 하고자 는 제네릭이 아닌 기본 인터페이스에서 GetEnumerator를 숨길. 당신은 결코 당신이 이전 버전과의 compat 상황에서 명시 적 아니라면 그 일이 호출되고 싶지 않습니다.

그것만으로도 방법 숨기기가 정당화됩니다. 메서드 숨김의 정당화에 대한 자세한 내용 은 주제에 대한 내 기사를 참조하십시오 .

"new"없이 숨기면 경고가 발생하는 이유는 무엇입니까?

우리는 당신이 무언가를 숨기고 있고 우연히 그것을 할 수 있다는 것을 당신에게 알리고 싶기 때문입니다. 파생 클래스를 편집하는 대신 다른 사람이 기본 클래스를 편집하여 실수로 무언가를 숨기고있을 수 있습니다.

왜 "새로운"없이 숨는 것은 오류가 아니라 경고입니까?

같은 이유. 새 버전의 기본 클래스를 선택했기 때문에 실수로 무언가를 숨기고있을 수 있습니다. 이것은 항상 발생합니다. FooCorp는 기본 클래스 B를 만듭니다. BarCorp는 고객이 해당 메서드를 좋아하기 때문에 Bar 메서드를 사용하여 파생 클래스 D를 만듭니다. FooCorp는 그것을보고 좋은 생각이라고 말합니다. 우리는 그 기능을 기본 클래스에 넣을 수 있습니다. 그들은 그렇게하고 Foo.DLL의 새 버전을 제공하고 BarCorp가 새 버전을 선택할 때 자신의 메서드가 이제 기본 클래스 메서드를 숨긴다는 말을 들으면 좋을 것입니다.

우리는 그 상황이 오류가 아닌 경고가 되기를 원합니다. 왜냐하면이를 오류로 만드는 것은 이것이 취약한 기본 클래스 문제의 또 다른 형태 임을 의미하기 때문 입니다. C #은 누군가 기본 클래스를 변경할 때 파생 클래스를 사용하는 코드에 미치는 영향을 최소화하도록 신중하게 설계되었습니다.

숨기고 기본값을 재정의하지 않는 이유는 무엇입니까?

가상 재정의는 위험하기 때문 입니다. 가상 재정의를 사용하면 파생 클래스가 기본 클래스를 사용하도록 컴파일 된 코드의 동작을 변경할 수 있습니다. 당신이 뭔가해야 재정의를 만드는 같은 뭔가 위험한 일을 의식적으로 그리고 의도적으로 하지 사고에 의해를.


1
FWIW, 나는“[t] 모자만으로 방법 은닉을 정당화한다”는 데 동의하지 않습니다. 때로는 이전 버전과의 호환성을 깨는 것이 더 낫습니다. Python 3은 이것이 실제로 너무 많은 작업을 파괴하지 않고 작동한다는 것을 보여주었습니다. "어떠한 비용으로도 이전 버전과의 호환성"이 유일한 합리적인 옵션은 아니라는 점에 유의하는 것이 중요합니다. 그리고이 이유는 다른 요점 으로 이어집니다. 기본 클래스를 변경하는 것은 항상 깨지기 쉽습니다. 실제로 종속 빌드를 중단하는 것이 바람직 할 수 있습니다. (그러나 실제 질문 아래의 내 의견에있는 질문에 답하려면 +1.)
Konrad Rudolph

@Konrad : 이전 버전의 호환성이 손상 될 수 있다는 데 동의합니다. Mono는 향후 릴리스에서 1.1에 대한 지원을 중단하기로 결정했습니다.
simendsjo

1
@Konrad : 귀하의 언어와의 하위 호환성을 깨는 것은 귀하의 언어를 사용하는 사람들이 자신의 코드와의 하위 호환성을 쉽게 깨뜨리는 것과는 매우 다릅니다. Eric의 예는 두 번째 경우입니다. 그리고 그 코드를 작성하는 사람은 이전 버전과의 호환성을 원한다고 결정했습니다. 언어는 가능하면 그 결정을 뒷받침해야합니다.
Brian

신이 대답 해, 에릭. 그러나 기본적으로 재정의하는 것이 나쁜 이유가 더 있습니다. 기본 클래스 Base 가 파생 클래스 Derived에서 기존 메서드 Duck () (Mario duck을 만듭니다)과 동일한 시그니처를 갖는 새로운 메서드 Duck () (오리 개체를 반환)을 도입하는 경우 당신과 Base의 작가), 당신 은 두 클래스의 클라이언트가 오리를 원할 때 마리오 오리를 만드는 것을 원하지 않습니다 .
jyoungdev

요약하면 대부분 기본 클래스 및 인터페이스 변경과 관련이 있습니다.
jyoungdev 2010-06-26

15

파생 클래스의 메서드 앞에 new 키워드가있는 경우 메서드는 기본 클래스의 메서드와 독립적 인 것으로 정의됩니다.

그러나 new 또는 override를 지정하지 않으면 결과 출력은 new를 지정한 것과 동일하지만 컴파일러 경고가 표시됩니다 (기본 클래스 메서드에서 메서드를 숨기고 있다는 것을 알지 못할 수 있으므로 또는 실제로 재정의하고 싶을 수 있으며 키워드를 포함하는 것을 잊었습니다.)

따라서 실수를 방지하고 원하는 작업을 명시 적으로 표시하고 코드를 더 읽기 쉽게 만들어 코드를 쉽게 이해할 수 있습니다.


12

이 컨텍스트에서 의 유일한 효과 new는 경고를 억제하는 것입니다. 의미 체계에는 변화가 없습니다.

따라서 한 가지 대답은 다음과 같습니다. new숨김이 의도적 이라는 것을 컴파일러에 알리고 경고를 제거해야합니다.

후속 질문은 : 메서드를 재정의 할 수 없거나 재정의 할 수없는 경우 동일한 이름의 다른 메서드를 도입하는 이유는 무엇입니까? 은폐는 본질적으로 이름 충돌이기 때문입니다. 물론 대부분의 경우 피할 것입니다.

의도적으로 숨겨야한다고 생각할 수있는 유일한 이유는 인터페이스가 이름을 강요 할 때입니다.


6

C #에서 멤버는 기본적으로 봉인되어 있습니다. 즉, virtual또는 abstract키워드 로 표시되지 않는 한이를 재정의 할 수 없으며 이는 성능상의 이유 때문 입니다. 새로운 수정은 명시 적으로 상속 된 멤버를 숨기는 데 사용됩니다.


4
그러나 새로운 수정자를 언제 사용하고 싶습니까?
simendsjo 2010-06-25

1
기본 클래스에서 상속 된 멤버를 명시 적으로 숨기려는 경우. 상속 된 멤버를 숨기면 멤버의 파생 버전이 기본 클래스 버전을 대체합니다.
Darin Dimitrov

3
그러나 기본 클래스를 사용하는 모든 메서드는 파생 된 것이 아니라 기본 구현을 계속 호출합니다. 이것은 나에게 위험한 상황 인 것 같습니다.
simendsjo 2010-06-25

@simendsjo, @Darin Dimitrov : 또한 새 키워드가 정말로 필요한 사용 사례에 대해 생각하기가 어렵습니다. 항상 더 나은 방법이있는 것 같습니다. 또한 기본 클래스 구현을 숨기는 것이 오류로 쉽게 이어지는 디자인 결함이 아닌지 궁금합니다.
Dirk Vollmar 2010-06-25

2

override키워드 를 지정하지 않고 재정의가 기본값 인 경우 이름이 같기 때문에 실수로 기본 메서드를 재정의 할 수 있습니다.

.Net 컴파일러 전략은 안전을 위해 무언가 잘못 될 수있는 경우 경고를 내보내는 것이므로이 경우 재정의가 기본값 인 경우 재정의 된 각 메서드에 대해 경고가 표시되어야합니다. 재정의 '.


0

내 추측은 주로 다중 인터페이스 상속 때문입니다. 신중한 인터페이스를 사용하면 두 개의 서로 다른 인터페이스가 동일한 메서드 서명을 사용할 수 있습니다. new키워드 의 사용을 허용하면 두 개의 별개의 클래스를 만드는 대신 하나의 클래스로 이러한 다른 구현을 만들 수 있습니다.

업데이트 ... Eric이이 예제를 개선하는 방법에 대한 아이디어를 제공했습니다.

public interface IAction1
{
    int DoWork();
}
public interface IAction2
{
    string DoWork();
}

public class MyBase : IAction1
{
    public int DoWork() { return 0; }
}
public class MyClass : MyBase, IAction2
{
    public new string DoWork() { return "Hi"; }
}

class Program
{
    static void Main(string[] args)
    {
        var myClass = new MyClass();
        var ret0 = myClass.DoWork(); //Hi
        var ret1 = ((IAction1)myClass).DoWork(); //0
        var ret2 = ((IAction2)myClass).DoWork(); //Hi
        var ret3 = ((MyBase)myClass).DoWork(); //0
        var ret4 = ((MyClass)myClass).DoWork(); //Hi
    }
}

1
여러 인터페이스를 사용하는 경우 인터페이스 메서드의 이름을 명시 적으로 지정하여 데이터 정렬을 방지 할 수 있습니다. "한 클래스에 대해 다른 구현"이 의미하는 바를 이해하지 못합니다. 둘 다 동일한 서명을 가지고 있습니다.
simendsjo

new당신은 또한 그 시점에서의 모든 자녀에 대한 상속 기반을 변경할 수 있습니다 키워드.
Matthew Whited 2010-06-25

-1

언급했듯이, 메소드 / 속성 숨김은 다른 방법으로는 쉽게 변경할 수없는 메소드 또는 속성에 대한 변경을 가능하게합니다. 이것이 유용 할 수있는 한 가지 상황은 상속 된 클래스가 기본 클래스에서 읽기 전용 인 읽기-쓰기 속성을 갖도록 허용하는 것입니다. 예를 들어 기본 클래스에 Value1-Value40이라는 읽기 전용 속성이 있다고 가정합니다 (물론 실제 클래스는 더 나은 이름을 사용합니다). 이 클래스의 봉인 된 하위 항목에는 기본 클래스의 개체를 가져와 여기에서 값을 복사하는 생성자가 있습니다. 그 후에는 클래스가 변경되는 것을 허용하지 않습니다. 상속 가능한 다른 하위 항목은 Value1-Value40이라는 읽기-쓰기 속성을 선언합니다.이 속성은 읽을 때 기본 클래스 버전과 동일하게 동작하지만 쓸 때는 값을 쓸 수 있습니다.

이 접근 방식 (아마 누군가 나를 도울 수 있음)의 한 가지 성가신 점은 동일한 클래스 내에서 특정 속성을 숨기고 재정의하는 방법을 모른다는 것입니다. CLR 언어가이를 허용합니까 (VB 2005 사용)? 기본 클래스 개체와 해당 속성이 추상적 인 경우 유용하지만 하위 클래스가 속성을 섀도 잉하기 전에 Value1에서 Value40 속성을 재정의하려면 중간 클래스가 필요합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.