파생 클래스에서 메서드를 호출하면 기본 클래스 메서드가 호출되는 이유는 무엇입니까?


146

이 코드를 고려하십시오.

class Program
{
    static void Main(string[] args)
    {
        Person person = new Teacher();
        person.ShowInfo();
        Console.ReadLine();
    }
}

public class Person
{
    public void ShowInfo()
    {
        Console.WriteLine("I am Person");
    }
}
public class Teacher : Person
{
    public new void ShowInfo()
    {
        Console.WriteLine("I am Teacher");
    }
}

이 코드를 실행하면 다음이 출력됩니다.

나는 사람이다

그러나의 인스턴스가 Teacher아닌의 인스턴스임을 알 수 있습니다 Person. 왜 코드가 그렇게합니까?


3
Java 사용자의 질문 : Console.ReadLine (); 이 예에 필요한가?
Rich

2
@Shahrooz 나는 당신의 질문에 대답 할 수 없습니다-나는 C #을 모른다. 매우 사소한 C # 질문을했습니다. Person 및 Teacher 클래스에서 WriteLine을 호출하려면 기본 메서드에서 ReadLine을 호출해야하는지 여부입니다.
Rich

6
예, .Net은 Main ()이 종료되면 콘솔 창을 자동으로 닫습니다. 이 문제를 해결하기 위해 Console.Read () 또는 Console.Readline ()을 사용하여 추가 입력을 기다렸다가 콘솔이 열린 상태로 유지되도록합니다.
켄 파치 선장

15
@Rich 아니요, 필요 하지는 않지만 종종 이러한 이유로 볼 수 있습니다. Visual Studio에서 콘솔 프로그램을 실행할 때 프로그램이 종료되면 명령 창이 닫히므로 프로그램 출력 을 보려면 프로그램 출력을 알려주십시오. 기다리다.
AakashM

1
@AakashM Thanks-콘솔이 Eclipse 창의 일부인 Eclipse에서 시간을 보내므로 닫히지 않습니다. 완벽하게 이해됩니다.
Rich

답변:


368

newvirtual/ 사이에는 차이가 있습니다 override.

인스턴스화 될 때 클래스는 포인터의 테이블에 지나지 않으며 실제 메소드 구현을 가리 킵니다. 다음 이미지는 이것을 잘 시각화해야합니다.

메소드 구현의 예

이제 방법이 정의 될 수있는 여러 가지 방법이 있습니다. 상속과 함께 사용될 때 각각 다르게 동작합니다. 표준 방식은 항상 위의 그림과 같이 작동합니다. 이 동작을 변경하려면 다른 키워드를 분석법에 첨부 할 수 있습니다.

1. 추상 수업

첫 번째는 abstract입니다. abstract방법은 단순히 아무데도 가리지 않습니다.

추상 클래스의 그림

클래스에 추상 멤버가 포함 된 경우으로 표시 abstract해야합니다. 그렇지 않으면 컴파일러가 애플리케이션을 컴파일하지 않습니다. abstract클래스의 인스턴스를 작성할 수는 없지만 클래스에서 상속하고 상속 된 클래스의 인스턴스를 작성하고 기본 클래스 정의를 사용하여 액세스 할 수 있습니다. 귀하의 예에서 이것은 다음과 같습니다.

public abstract class Person
{
    public abstract void ShowInfo();
}

public class Teacher : Person
{
    public override void ShowInfo()
    {
        Console.WriteLine("I am a teacher!");
    }
}

public class Student : Person
{
    public override void ShowInfo()
    {
        Console.WriteLine("I am a student!");
    }
}

호출되면 ShowInfo구현에 따라 동작이 달라집니다.

Person person = new Teacher();
person.ShowInfo();    // Shows 'I am a teacher!'

person = new Student();
person.ShowInfo();    // Shows 'I am a student!'

둘, StudentS와 TeacherS는 PersonS,하지만 그들은 자신에 대해 프롬프트 정보를 묻는 메시지가 나타나면 서로 다른 동작합니다. 그러나 정보를 묻도록 요청하는 방법은 동일합니다. Person클래스 인터페이스 사용.

그래서 당신이 상속 할 때 무대 뒤에서 어떻게됩니까 Person? 구현할 때 ShowInfo포인터가 더 이상 아무 것도 가리 키지 않고 이제 실제 구현을 가리 킵니다. Student인스턴스를 만들 때 Students를 가리 킵니다 ShowInfo.

상속 된 메소드의 일러스트

2. 가상 방법

두 번째 방법은 방법을 사용 virtual하는 것입니다. 기본 클래스에서 선택적 기본 구현을 제공한다는 점을 제외하면 동작은 동일 합니다. 와 클래스 virtual멤버를 통해 인스턴스 할 수 있지만 상속 된 클래스는 다른 구현을 제공 할 수 있습니다. 코드가 실제로 작동하는 모습은 다음과 같습니다.

public class Person
{
    public virtual void ShowInfo()
    {
        Console.WriteLine("I am a person!");
    }
}

public class Teacher : Person
{
    public override void ShowInfo()
    {
        Console.WriteLine("I am a teacher!");
    }
}

주요 차이점은 기본 멤버 Person.ShowInfo가 더 이상 아무 것도 가리 키지 않는다는 것입니다. 이것이 또한 인스턴스를 작성할 수있는 이유이기도합니다 Person(따라서 abstract더 이상 표시 하지 않아도 됨).

기본 클래스 내부의 가상 구성원 그림

이것은 현재 첫 번째 이미지와 다르지 않습니다. 그 이유는 virtual메소드가 " 표준 방식 " 구현을 가리키고 있기 때문 입니다. 사용하여 virtual, 당신은 말할 수 Persons그들은 것을, (안 한다 에 대해 서로 다른 구현을 제공) ShowInfo. 위와 override같이 다른 구현 (을 사용하여 ) 을 제공 Teacher하면 이미지는와 동일하게 보입니다 abstract. Students에 대한 사용자 정의 구현을 제공하지 않았다고 상상해보십시오 .

public class Student : Person
{
}

코드는 다음과 같이 호출됩니다.

Person person = new Teacher();
person.ShowInfo();    // Shows 'I am a teacher!'

person = new Student();
person.ShowInfo();    // Shows 'I am a person!'

그리고 이미지 Student는 다음과 같습니다.

가상 키워드를 사용하는 메소드의 기본 구현 그림

3. 마법의 '새로운'키워드 (일명 "그림자")

new이 주위에 더 많은 해킹입니다. 기본 클래스 / 인터페이스의 메소드와 이름이 같은 일반화 된 클래스의 메소드를 제공 할 수 있습니다. 둘 다 자신의 사용자 정의 구현을 가리 킵니다.

새 키워드를 사용한 "주변"그림

구현은 제공 한 것과 유사합니다. 메소드에 액세스하는 방식에 따라 동작이 다릅니다.

Teacher teacher = new Teacher();
Person person = (Person)teacher;

teacher.ShowInfo();    // Prints 'I am a teacher!'
person.ShowInfo();     // Prints 'I am a person!'

이 동작을 원할 수는 있지만 오해의 소지가 있습니다.

이것이 당신을 위해 이해하기 쉽기를 바랍니다!


9
큰 답변을 주셔서 감사합니다

6
이 다이어그램을 생성하기 위해 무엇을 사용 했습니까?
BlueRaja-대니 Pflughoeft

2
훌륭하고 철저한 답변.
Nik Bougalis

8
tl; dr 당신 new은 함수의 상속을 중단하고 새로운 함수를 수퍼 클래스 함수와 분리시키는 데 사용했습니다
ratchet freak

3
@ 테이 몬 : 사실 아니에요 ... 난 그냥 전화가 Person아니라 Student;) 에 대해 진행되고 있음을 분명히하고 싶었다 .)
Carsten

45

C #의 하위 유형 다형성은 C ++과 유사하지만 Java와는 달리 명시 적 가상 성을 사용합니다. 즉, 메서드를 재정의 가능한 것으로 명시 적으로 표시해야합니다 (예 :) virtual. C #에서는 override오타를 방지하기 위해 재정의 메서드를 재정의 (예 :)로 명시 적으로 표시해야 합니다.

public class Person
{
    public virtual void ShowInfo()
    {
        Console.WriteLine("I am Person");
    }
}

public class Teacher : Person
{
    public override void ShowInfo()
    {
        Console.WriteLine("I am Teacher");
    }
}

질문의 코드에서는 재정의 대신 그림자 를 사용 new하는 을 사용 합니다. 섀도 잉은 런타임 시맨틱이 아닌 컴파일 타임 시맨틱에만 영향을 미치므로 의도하지 않은 출력이됩니다.


4
누가 OP가 그 말의 의미를 알고 있다고 말할까요?
Cole Johnson

@ColeJohnson 설명을 추가하겠습니다.

25

부모 클래스 참조에 넣은 클래스 객체의 메서드를 호출하려면 메서드를 가상 으로 만들어야하고 자식 클래스의 함수를 재정의해야합니다.

public class Person
{
    public virtual void ShowInfo()
    {
        Console.WriteLine("I am Person");
    }
}
public class Teacher : Person
{
    public override void ShowInfo()
    {
        Console.WriteLine("I am Teacher");
    }
}

가상 방법

가상 메소드가 호출되면 오브젝트의 런타임 유형이 대체 멤버를 점검합니다. 파생 클래스가 멤버를 재정의하지 않은 경우 가장 파생 된 클래스의 재정의 멤버가 호출됩니다.이 멤버는 원래 멤버 일 수 있습니다. 기본적으로 메소드는 비 가상적입니다. 비가 상 방법을 재정의 할 수 없습니다. 정적, 추상, 개인 또는 대체 수정 자인 MSDN 과 함께 가상 수정자를 사용할 수 없습니다 .

섀도 잉에 New 사용

재정의 대신 새로운 키워드를 사용하고 있습니다. 이것이 새로운 기능입니다.

  • 파생 클래스의 메서드 앞에 new 또는 override 키워드가 없으면 컴파일러에서 경고를 표시하고 새 키워드가있는 것처럼 동작합니다.

  • 파생 클래스메서드 앞에 new 키워드가있는 경우이 메서드는 기본 클래스의 메서드와 독립적 인 것으로 정의됩니다 .이 MSDN 문서에서는 이를 잘 설명합니다.

초기 바인딩 VS 늦은 바인딩

컴파일 할 때 컴파일러가 객체 대신 참조 유형 (기본 클래스)의 기본 클래스의 메소드에 대한 호출을 바인딩 하는 현재의 경우 (가상이 아닌) 일반 메소드에 대한 초기 바인딩 이 있습니다. 클래스 즉, 파생 클래스 객체 . ShowInfo가상 방법 이 아니기 때문 입니다. 늦은 바인딩은 가상 메소드 테이블 (vtable)을 사용하여 런타임에 (가상 / 재정의 메소드) 수행됩니다 .

정상적인 함수의 경우 컴파일러는 메모리에서 숫자 위치를 계산할 수 있습니다. 그러면 함수가 호출 될 때이 주소에서 함수를 호출하는 명령을 생성 할 수 있습니다.

가상 메소드가있는 오브젝트의 경우 컴파일러는 v-table을 생성합니다. 이것은 본질적으로 가상 메소드의 주소를 포함하는 배열입니다. 가상 메서드가있는 모든 개체에는 v- 테이블의 주소 인 컴파일러에서 생성 된 숨겨진 멤버가 포함됩니다. 가상 함수가 호출되면 컴파일러는 v-table에서 적절한 방법의 위치를 ​​계산합니다. 그런 다음 객체 v-table에서 코드를 생성하고이 위치에서 가상 메소드를 호출합니다 ( Reference) .


7

Achratt의 답변작성 하고 싶습니다 . 완전성을 위해, 차이점은 OP가 new파생 클래스의 메소드에서 키워드가 기본 클래스 메소드를 대체 할 것으로 예상한다는 것입니다. 실제로하는 것은 기본 클래스 메소드를 숨기는 것입니다.

C #에서 다른 답변이 언급했듯이 기존의 메서드 재정의는 명시 적이어야합니다. 기본 클래스 메소드는로 표시되어야 virtual하고 파생 클래스는 구체적 override으로 기본 클래스 메소드 여야합니다 . 이것이 완료되면 객체가 기본 클래스 또는 파생 클래스의 인스턴스로 취급되는지 여부는 중요하지 않습니다. 파생 된 메소드를 찾아서 호출합니다. 이것은 C ++에서와 비슷한 방식으로 수행됩니다. 컴파일 될 때 "가상"또는 "재정의"로 표시된 메소드는 참조 된 오브젝트의 실제 유형을 판별하고 변수 유형에서 실제 오브젝트 유형으로 트리를 따라 아래로 오브젝트 계층 구조를 순회하여 "지연"(런타임)으로 분석됩니다. 변수 유형으로 정의 된 메소드의 가장 파생 된 구현을 찾으십시오.

이것은 "암시 적 재정의"를 허용하는 Java와 다릅니다. 예를 들어 메소드 (정적이 아닌)의 경우, 동일한 서명 (이름 및 번호 / 유형의 매개 변수)의 메소드를 정의하면 서브 클래스가 수퍼 클래스를 대체합니다.

제어하지 않는 비가 상 메서드의 기능을 확장하거나 재정의하는 것이 유용한 경우가 많으므로 C #에는 new문맥 키워드 도 포함됩니다 . new대신을 재정의 키워드 "가죽"부모 방법. 상속 가능한 메소드는 가상이든 아니든 숨길 수 있습니다. 이를 통해 개발자는 그렇지 않은 멤버를 해결하지 않고도 부모로부터 상속하려는 멤버를 활용할 수 있으며 코드 소비자에게 동일한 "인터페이스"를 제공 할 수 있습니다.

숨기기는 숨기기 방법이 정의 된 상속 수준 이하에서 객체를 사용하여 사람의 관점에서 재정의하는 것과 유사하게 작동합니다. 질문의 예에서 Teacher를 만들고 Teacher 유형의 변수에 해당 참조를 저장하는 코더는 Teacher에서 ShowInfo () 구현의 동작을 볼 수 있습니다. 그러나 Person 레코드 컬렉션에서 객체를 사용하는 사람은 ShowInfo ()의 Person 구현 동작을 볼 수 있습니다. Teacher의 메소드는 부모 (Person.ShowInfo ()도 가상이어야 함)를 대체하지 않기 때문에 Person 추상화 레벨에서 작업하는 코드는 Teacher 구현을 찾지 못하고 사용하지 않습니다.

또한 new키워드가이를 명시 적으로 수행 할 뿐만 아니라 C #에서는 암시 적 메서드 숨기기를 허용합니다. override또는 없이 부모 클래스 메서드와 동일한 서명을 가진 메서드를 정의하면 메서드 new가 숨겨집니다 (ReSharper 또는 CodeRush와 같은 특정 리팩토링 도우미에서 컴파일러 경고 또는 불만 사항이 발생하지만). 이것이 C #의 디자이너가 C ++의 명시 적 재정의와 Java의 암시 적 재정의 사이에서 겪은 타협입니다. 우아하지만 우아하지만 이전 언어 중 하나의 배경에서 온 경우 항상 예상되는 동작을 생성하지는 않습니다.

새로운 내용은 다음과 같습니다 . 긴 상속 체인에서 두 키워드를 결합하면 복잡해집니다. 다음을 고려하세요:

class Foo { public virtual void DoFoo() { Console.WriteLine("Foo"); } }
class Bar:Foo { public override sealed void DoFoo() { Console.WriteLine("Bar"); } }
class Baz:Bar { public virtual void DoFoo() { Console.WriteLine("Baz"); } }
class Bai:Baz { public override void DoFoo() { Console.WriteLine("Bai"); } }
class Bat:Bai { public new void DoFoo() { Console.WriteLine("Bat"); } }
class Bak:Bat { }

Foo foo = new Foo();
Bar bar = new Bar();
Baz baz = new Baz();
Bai bai = new Bai();
Bat bat = new Bat();

foo.DoFoo();
bar.DoFoo();
baz.DoFoo();
bai.DoFoo();
bat.DoFoo();

Console.WriteLine("---");

Foo foo2 = bar;
Bar bar2 = baz;
Baz baz2 = bai;
Bai bai2 = bat;
Bat bat2 = new Bak();

foo2.DoFoo();
bar2.DoFoo();
baz2.DoFoo();
bai2.DoFoo();    

Console.WriteLine("---");

Foo foo3 = bak;
Bar bar3 = bak;
Baz baz3 = bak;
Bai bai3 = bak;
Bat bat3 = bak;

foo3.DoFoo();
bar3.DoFoo();
baz3.DoFoo();
bai3.DoFoo();    
bat3.DoFoo();

산출:

Foo
Bar
Baz
Bai
Bat
---
Bar
Bar
Bai
Bai
Bat
---
Bar
Bar
Bai
Bai
Bat

첫 번째 다섯 세트는 모두 예상됩니다. 각 레벨에는 구현이 있으며 인스턴스화 한 것과 동일한 유형의 오브젝트로 참조되므로 런타임은 변수 유형이 참조하는 상속 레벨에 대한 각 호출을 분석합니다.

두 번째 5 세트는 각 인스턴스를 직계 상위 유형의 변수에 할당 한 결과입니다. 이제는 행동의 차이가 있습니다. foo2는 실제로 Bar캐스트 Foo된이며 실제 객체 유형 Bar의 파생 된 메소드를 계속 찾습니다. bar2A는 Baz하지만,과는 달리 foo2바즈가 명시 적으로 바의 구현을 재정의하지 않기 때문에, (이 할 수없는 바 sealed"하향식 (top-down)"를 찾고 때), 그것은 런타임에서 볼 수없는, 그래서 바의 구현 대신이라고합니다. Baz는 new키워드 를 사용할 필요가 없습니다 . 키워드를 생략하면 컴파일러 경고가 표시되지만 C #에서 암시적인 동작은 부모 메서드를 숨기는 것입니다. baz2A는 Bai, 어떤 재정 Baznew구현이므로 동작은와 비슷합니다 foo2. Bai에서 실제 객체 유형의 구현이 호출됩니다. bai2Bat부모 Bai의 메소드 구현 을 다시 숨기고 bar2Bai의 구현이 봉인되지 않은 것과 동일하게 작동 하므로 이론적으로 Bat이 메소드를 숨기는 대신 대체 할 수 있습니다. 마지막으로, bat2A는 Bak두 종류의 오버라이드 (override) 구현이없는, 단순히 부모의 것을 사용합니다.

세 번째 다섯 세트는 전체 하향식 해결 동작을 보여줍니다. 모든 것은 실제로 체인에서 가장 파생 된 클래스의 인스턴스를 참조 Bak하지만 모든 수준의 변수 유형에서 해결은 상속 체인의 해당 수준에서 시작하여 가장 파생 된 메서드의 명시 적 재정의로 드릴 다운하여 수행 됩니다. 이들의 Bar, Bai,와 Bat. 따라서 메소드 숨기기는 우선하는 상속 체인을 "파괴"합니다. 숨기기 방법을 사용하려면 메서드를 숨기는 상속 수준 또는 그 이하의 객체로 작업해야합니다. 그렇지 않으면 숨겨진 방법이 "발견"되어 대신 사용됩니다.


4

C # : Polymorphism (C # Programming Guide)의 다형성에 대해 읽으십시오

다음은 그 예입니다.

새 키워드를 사용하면 교체 된 기본 클래스 멤버 대신 새 클래스 멤버가 호출됩니다. 이러한 기본 클래스 멤버를 숨겨진 멤버라고합니다. 파생 클래스의 인스턴스가 기본 클래스의 인스턴스로 캐스팅 된 경우에도 숨겨진 클래스 멤버를 호출 할 수 있습니다. 예를 들면 다음과 같습니다.

DerivedClass B = new DerivedClass();
B.DoWork();  // Calls the new method.

BaseClass A = (BaseClass)B;
A.DoWork();  // Calls the old method.

3

그것을 virtual만든 다음에서 해당 기능을 재정의해야합니다 Teacher. 파생 클래스를 참조하기 위해 기본 포인터를 상속하고 사용함에 따라를 사용하여 재정의해야합니다 virtual. 클래스 참조가 아닌 파생 클래스 참조 new에서 base클래스 메서드 를 숨기는 데 사용됩니다 base.


3

이 정보를 확장하기 위해 몇 가지 예를 더 추가하고 싶습니다. 이것이 도움이되기를 바랍니다.

다음은 파생 유형이 기본 유형에 할당 될 때 발생하는 주변 환경을 정리하는 코드 샘플입니다. 이 컨텍스트에서 사용 가능한 메소드와 대체 된 메소드와 숨겨진 메소드의 차이점

namespace TestApp
{
    class Program
    {
        static void Main(string[] args)
        {
            A a = new A();
            a.foo();        // A.foo()
            a.foo2();       // A.foo2()

            a = new B();    
            a.foo();        // B.foo()
            a.foo2();       // A.foo2()
            //a.novel() is not available here

            a = new C();
            a.foo();        // C.foo()
            a.foo2();       // A.foo2()

            B b1 = (B)a;    
            b1.foo();       // C.foo()
            b1.foo2();      // B.foo2()
            b1.novel();     // B.novel()

            Console.ReadLine();
        }
    }


    class A
    {
        public virtual void foo()
        {
            Console.WriteLine("A.foo()");
        }

        public void foo2()
        {
            Console.WriteLine("A.foo2()");
        }
    }

    class B : A
    {
        public override void foo()
        {
            // This is an override
            Console.WriteLine("B.foo()");
        }

        public new void foo2()      // Using the 'new' keyword doesn't make a difference
        {
            Console.WriteLine("B.foo2()");
        }

        public void novel()
        {
            Console.WriteLine("B.novel()");
        }
    }

    class C : B
    {
        public override void foo()
        {
            Console.WriteLine("C.foo()");
        }

        public new void foo2()
        {
            Console.WriteLine("C.foo2()");
        }
    }
}

또 다른 작은 예외는 다음 코드 줄에 대한 것입니다.

A a = new B();    
a.foo(); 

VS 컴파일러 (지능형)는 a.foo ()를 A.foo ()로 표시합니다.

따라서 더 파생 된 형식이 기본 형식에 할당 된 경우 파생 형식에서 재정의 된 메서드가 참조 될 때까지 '기본 형식'변수가 기본 형식으로 작동합니다. 이것은 부모와 자식 유형 사이에 동일한 이름을 갖지만 재정의되지 않은 숨겨진 메서드 또는 메서드를 사용하면 약간의 직관적이지 않을 수 있습니다.

이 코드 샘플은 이러한 경고를 설명하는 데 도움이됩니다!


2

부모 / 자식 클래스 재정의 동작에서 C #은 java와 다릅니다. Java에서는 기본적으로 모든 메소드가 가상이므로 원하는 동작이 기본적으로 지원됩니다.

C #에서는 기본 클래스에서 메서드를 가상으로 표시해야합니다. 그러면 원하는 것을 얻을 수 있습니다.


2

새로운 키워드 텔이 현재 클래스의 메서드에서이 유형 교사의 변수에 저장된 클래스 교사의 인스턴스가있는 경우에만 작동합니다. 또는 캐스팅을 사용하여 트리거 할 수 있습니다 : ((Teacher) Person) .ShowInfo ()


1

변수 '교사'의 유형은 여기 있으며이 typeof(Person)유형은 Teacher 클래스에 대해 아무것도 모르며 파생 된 유형의 메소드를 찾지 않습니다. Teacher 클래스의 메소드를 호출하려면 변수를 캐스트해야합니다 (person as Teacher).ShowInfo().

값 유형을 기준으로 특정 메소드를 호출하려면 기본 클래스에서 키워드 'virtual'을 사용하고 파생 클래스의 가상 메소드를 대체해야합니다. 이 방법을 사용하면 가상 메서드를 재정의하거나 사용하지 않고 파생 클래스를 구현할 수 있습니다. 기본 클래스의 메소드는 오버라이드 된 가상이없는 유형에 대해 호출됩니다.

public class Program
{
    private static void Main(string[] args)
    {
        Person teacher = new Teacher();
        teacher.ShowInfo();

        Person incognito = new IncognitoPerson ();
        incognito.ShowInfo();

        Console.ReadLine();
    }
}

public class Person
{
    public virtual void ShowInfo()
    {
        Console.WriteLine("I am Person");
    }
}

public class Teacher : Person
{
    public override void ShowInfo()
    {
        Console.WriteLine("I am Teacher");
    }
}

public class IncognitoPerson : Person
{

}

1

너무 늦을 수도 있습니다 ... 그러나 질문은 간단하며 대답은 같은 수준의 복잡성을 가져야합니다.

코드 변수에서 person은 Teacher.ShowInfo ()에 대해 아무것도 모릅니다. 가상이 아니기 때문에 기본 클래스 참조에서 마지막 메소드를 호출하는 방법이 없습니다.

상속에는 유용한 접근 방법이 있습니다. 코드 계층 구조로 무엇을 말하고 싶은지 상상해보십시오. 또한 하나 또는 다른 도구가 자신에 대해 무엇을 말하는지 상상해보십시오. 예를 들어 기본 클래스에 가상 함수를 추가하면 다음과 같이 가정합니다. 1. 기본 구현이 가능합니다. 2. 파생 클래스에서 다시 구현 될 수 있습니다. 추상 함수를 추가하면 한 가지만 의미합니다-서브 클래스는 구현을 만들어야합니다. 그러나 일반 기능이있는 경우 다른 사람이 구현을 변경하지 않아도됩니다.


0

컴파일러는이 사실을 모르기 때문에이 작업을 수행합니다 Teacher. 그것이 아는 것은 그것이 그것 Person에서 파생 된 것입니다. 따라서 할 수있는 모든 것은 Person.ShowInfo()메소드를 호출하는 것입니다.


0

간단히 대답하고 싶었습니다.

당신은 사용해야 virtual하고 override수업에 그 오버라이드 (override) 할 수 있습니다. 사용 virtual자식 사용하는 클래스에 의해 오버라이드 (override) 할 수있는 방법에 대한 override이러한 오버라이드 (override) 할 방법에 대한 virtual방법을.


0

일부 변경 사항을 제외하고 Java에서 위에서 언급 한 것과 동일한 코드를 작성했으며 예외로 정상적으로 작동했습니다. 기본 클래스의 메소드가 대체되어 표시되는 결과는 "I am Teacher"입니다.

이유 : 실제로 파생 클래스의 참조를 포함하는 기본 클래스 (파생 클래스의 참조 인스턴스를 가질 수 있음)에 대한 참조를 작성 중입니다. 그리고 우리가 알고있는 것처럼 인스턴스는 항상 메서드를 먼저 찾은 다음 실행하고, 정의를 찾지 못하면 계층 구조로 올라갑니다.

public class inheritance{

    public static void main(String[] args){

        Person person = new Teacher();
        person.ShowInfo();
    }
}

class Person{

    public void ShowInfo(){
        System.out.println("I am Person");
    }
}

class Teacher extends Person{

    public void ShowInfo(){
        System.out.println("I am Teacher");
    }
}

0

Keith S.의 뛰어난 데모와 다른 모든 사람의 품질 답변을 바탕으로, 완전성 (Uber 완전성)을 위해 명시적인 인터페이스 구현을 진행하여 그 작동 방식을 시연 할 수 있습니다. 아래를 고려하십시오.

네임 스페이스 LinqConsoleApp {

class Program
{

    static void Main(string[] args)
    {


        Person person = new Teacher();
        Console.Write(GetMemberName(() => person) + ": ");
        person.ShowInfo();

        Teacher teacher = new Teacher();
        Console.Write(GetMemberName(() => teacher) + ": ");
        teacher.ShowInfo();

        IPerson person1 = new Teacher();
        Console.Write(GetMemberName(() => person1) + ": ");
        person1.ShowInfo();

        IPerson person2 = (IPerson)teacher;
        Console.Write(GetMemberName(() => person2) + ": ");
        person2.ShowInfo();

        Teacher teacher1 = (Teacher)person1;
        Console.Write(GetMemberName(() => teacher1) + ": ");
        teacher1.ShowInfo();

        Person person4 = new Person();
        Console.Write(GetMemberName(() => person4) + ": ");
        person4.ShowInfo();

        IPerson person3 = new Person();
        Console.Write(GetMemberName(() => person3) + ": ");
        person3.ShowInfo();

        Console.WriteLine();

        Console.ReadLine();

    }

    private static string GetMemberName<T>(Expression<Func<T>> memberExpression)
    {
        MemberExpression expressionBody = (MemberExpression)memberExpression.Body;
        return expressionBody.Member.Name;
    }

}
interface IPerson
{
    void ShowInfo();
}
public class Person : IPerson
{
    public void ShowInfo()
    {
        Console.WriteLine("I am Person == " + this.GetType());
    }
    void IPerson.ShowInfo()
    {
        Console.WriteLine("I am interface Person == " + this.GetType());
    }
}
public class Teacher : Person, IPerson
{
    public void ShowInfo()
    {
        Console.WriteLine("I am Teacher == " + this.GetType());
    }
}

}

출력은 다음과 같습니다.

사람 : 나는 사람 == LinqConsoleApp.Teacher

교사 : 저는 교사입니다 == LinqConsoleApp.Teacher

person1 : 저는 교사입니다 == LinqConsoleApp.Teacher

person2 : 저는 교사입니다 == LinqConsoleApp.Teacher

teacher1 : 저는 교사입니다 == LinqConsoleApp.Teacher

person4 : 나는 Person == LinqConsoleApp.Person입니다

person3 : 인터페이스 Person == LinqConsoleApp.Person입니다.

두 가지 유의할 사항 :
Teacher.ShowInfo () 메서드는 새 키워드를 생략합니다. new를 생략하면 메소드 동작은 new 키워드가 명시 적으로 정의 된 것과 동일합니다.

가상 키워드와 함께 override 키워드 만 사용할 수 있습니다. 기본 클래스 메소드는 가상이어야합니다. 또는 추상적 인 경우 클래스는 추상적이어야합니다.

Teacher 클래스는 기본 구현 (가상 선언 없음)을 무시할 수없고 person은 .GetType (Teacher)이므로 Teacher 클래스의 구현을 숨기므로 person은 ShowInfo의 기본 구현을 가져옵니다.

teacher는 Typeof (Teacher)이고 Person 상속 수준이 아니기 때문에 ShowInfo의 파생 Teacher 구현을 가져옵니다.

person1은 .GetType (Teacher)이므로 파생 된 Teacher 구현을 가져오고 암시 된 새 키워드는 기본 구현을 숨 깁니다.

person2는 IPerson을 구현하고 IPerson에게 명시 적 캐스트를 가져 오더라도 파생 된 Teacher 구현을 가져옵니다. 이는 Teacher 클래스가 IPerson.ShowInfo () 메서드를 명시 적으로 구현하지 않기 때문입니다.

또한 teacher1은 .GetType (Teacher)이므로 파생 된 Teacher 구현을 가져옵니다.

Person 클래스 만이 메소드를 명시 적으로 구현하고 person3은 IPerson 유형의 인스턴스이므로 person3만이 ShowInfo의 IPerson 구현을 가져옵니다.

인터페이스를 명시 적으로 구현하려면 대상 인터페이스 유형의 var 인스턴스를 선언해야하며 클래스는 인터페이스 멤버를 명시 적으로 구현 (정규화)해야합니다.

person4조차도 IPerson.ShowInfo 구현을 얻지 못합니다. person4가 .GetType (Person)이고 Person이 IPerson을 구현하더라도 person4는 IPerson의 인스턴스가 아니기 때문입니다.


서식 코드를 올바르게 작성하는 데 약간의 어려움이 있습니다. 지금 당장 예쁘게 할 시간이 없습니다 ...
steely

0

LinQPad 샘플은 맹목적으로 시작하고 코드 중복을 줄입니다.

void Main()
{
    IEngineAction Test1 = new Test1Action();
    IEngineAction Test2 = new Test2Action();
    Test1.Execute("Test1");
    Test2.Execute("Test2");
}

public interface IEngineAction
{
    void Execute(string Parameter);
}

public abstract class EngineAction : IEngineAction
{
    protected abstract void PerformAction();
    protected string ForChildren;
    public void Execute(string Parameter)
    {  // Pretend this method encapsulates a 
       // lot of code you don't want to duplicate 
      ForChildren = Parameter;
      PerformAction();
    }
}

public class Test1Action : EngineAction
{
    protected override void PerformAction()
    {
        ("Performed: " + ForChildren).Dump();
    }
}

public class Test2Action : EngineAction
{
    protected override void PerformAction()
    {
        ("Actioned: " + ForChildren).Dump();
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.