Почему вызов метода в моем производном классе вызывает метод базового класса?

Рассмотрим этот код:

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. Почему код делает это?

Ответ 1

Там разница между new и virtual/override.

Вы можете себе представить, что класс, при создании экземпляра, представляет собой не что иное, как таблицу указателей, указывающую на фактическую реализацию его методов. Следующее изображение должно визуализировать это довольно хорошо:

Illustration of method implementations

Теперь существуют разные способы, метод может быть определен. Каждый из них ведет себя иначе, когда используется с наследованием. Стандартный способ всегда работает, как показано на рисунке выше. Если вы хотите изменить это поведение, вы можете прикрепить к нему разные ключевые слова.

1. Абстрактные классы

Первый - abstract. Методы abstract просто указывают на никуда:

Illustration of abstract classes

Если ваш класс содержит абстрактные элементы, он также должен быть помечен как 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!'

Оба, Student и Teacher являются Person s, но они ведут себя по-разному, когда их просят выслать информацию о себе. Тем не менее, способ попросить их запросить их информацию, тот же: использование интерфейса класса Person.

Итак, что происходит за кулисами, когда вы наследуете от Person? При реализации ShowInfo указатель больше не указывает нигде, теперь он указывает на фактическую реализацию! При создании экземпляра Student он указывает на Student ShowInfo:

Illustration of inherited methods

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):

Illustration of a virtual member inside a base class

Вы должны заметить, что это пока не похоже на первое изображение. Это связано с тем, что метод virtual указывает на реализацию "стандартный способ". Используя virtual, вы можете указать Persons, что они могут (не должны) предоставить другую реализацию для ShowInfo. Если вы предоставили другую реализацию (используя override), как я сделал для Teacher выше, изображение будет выглядеть так же, как и для abstract. Представьте себе, что для Student s:

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 будет выглядеть так:

Illustration of the default implementation of a method, using virtual-keyword

3. Магия "нового" ключевого слова aka "Тени"

new - это больше взломать это. Вы можете предоставлять методы в обобщенных классах, которые имеют те же имена, что и методы в базовом классе/интерфейсе. Оба указывают на собственную собственную реализацию:

Illustration of the "way around" using the new-keyword

Реализация похожа на ту, которую вы предоставили. Поведение отличается от того, как вы обращаетесь к методу:

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

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

Такое поведение может потребоваться, но в вашем случае оно вводит в заблуждение.

Надеюсь, это поможет вам понять вас!

Ответ 2

Политизм подтипов в С# использует явную виртуальность, похожую на С++, но в отличие от Java. Это означает, что вы явно должны отмечать методы как переопределяемые (т.е. virtual). В С# вы также должны явно отмечать переопределяющие методы как переопределяющие (т.е. 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, который затенение вместо переопределения. Shadowing просто влияет на семантику времени компиляции, а не на семантику времени выполнения, следовательно, на непредвиденный вывод.

Ответ 3

Вам нужно сделать метод virtual, и вы должны переопределить функцию в дочернем классе, чтобы вызвать метод класса объект, который вы помещаете в родительский класс.

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.

Использование нового для теней

Вы используете новое ключевое слово вместо переопределения, это то, что новое делает

  • Если методу производного класса не предшествуют новые или переопределяющие ключевые слова, компилятор выдаст предупреждение, и метод будет вести себя так, как если бы появилось новое ключевое слово.

  • Если методу в производном классе предшествует новое ключевое слово, метод определяется как независимый от метода в базовом классе, This статья MSDN объясняет это очень хорошо.

Раннее связывание VS Поздняя привязка

У нас есть раннее связывание во время компиляции для обычного метода (не виртуального), который является случаем потока, который компилятор будет привязывать вызов к методу базового класса, который является методом ссылочного типа (базового класса), а не объектом проведенных в рефери базового класса, т.е. объекта производного класса. Это связано с тем, что ShowInfo не является виртуальным методом. Последняя привязка выполняется во время выполнения для (виртуального/переопределенного метода) с помощью таблицы таблицы виртуальных методов (vtable).

Для нормальной функции компилятор может определить числовое местоположение его в памяти. Затем, когда функция вызывается, она может генерировать инструкция для вызова функции по этому адресу.

Для объекта, который имеет любые виртуальные методы, компилятор будет генерировать v-таблица. Это, по существу, массив, содержащий адреса виртуальные методы. Каждый объект, имеющий виртуальный метод, будет содержат скрытый член, сгенерированный компилятором, который является адресом v-таблицы. Когда вызывается виртуальная функция, компилятор будет определить, какая позиция имеет соответствующий метод в v-таблицы. Затем он сгенерирует код для поиска в объектах v-table и вызовите виртуальный метод в этой позиции, Ссылка.

Ответ 4

Я хочу построить ответ Achratt. Для полноты разница заключается в том, что OP ожидает, что ключевое слово new в методе производного класса переопределит метод базового класса. Фактически это скрывает метод базового класса.

В С#, как упоминалось выше, традиционный метод переопределения должен быть явным; метод базового класса должен быть помечен как virtual, а производный класс должен специально override использовать метод базового класса. Если это сделано, то не имеет значения, рассматривается ли объект как экземпляр базового класса или производного класса; найденный и вызванный производный метод. Это делается аналогично С++; метод с надписью "virtual" или "override" при компиляции разрешается "поздно" (во время выполнения) путем определения фактического типа объекта ссылки и прохождения иерархии объектов вниз по дереву от типа переменной до фактического типа объекта, чтобы найти наиболее производную реализацию метода, определенного типом переменной.

Это отличается от Java, что позволяет "неявные переопределения"; например, методы (нестатические), просто определяющие метод одной и той же сигнатуры (имя и номер/тип параметров), заставят подкласс переопределить суперкласс.

Поскольку часто полезно расширить или переопределить функциональность не виртуального метода, который вы не контролируете, С# также включает ключевое слово new. Ключевое слово new "скрывает" родительский метод вместо его переопределения. Любой наследуемый метод может быть скрыт независимо от того, является ли он виртуальным или нет; это позволяет вам, разработчику, использовать элементы, которые вы хотите наследовать от родителя, без необходимости работать с теми, которые у вас нет, в то же время позволяя вам представить тот же "интерфейс" для потребителей вашего кода.

Скрытие работает аналогично переопределению с точки зрения человека, использующего ваш объект, на уровне или ниже уровня наследования, при котором определяется метод укрытия. Из примера вопроса кодер, создающий Учителя и сохраняющий эту ссылку в переменной типа Учителя, увидит поведение реализации ShowInfo() от Учителя, которое скрывает его от Лица. Тем не менее, кто-то, работающий с вашим объектом в коллекции записей Person (как вы), увидит поведение реализации Person в ShowInfo(); потому что метод Учителя не переопределяет его родительский элемент (для чего также требуется, чтобы Person.ShowInfo() был виртуальным), код, работающий на уровне абстракции Person, не найдет реализацию Teacher и не будет использовать его.

Кроме того, не только ключевое слово new делает это явно, С# позволяет скрывать скрытый метод; просто определяя метод с той же сигнатурой, что и метод родительского класса, без override или new, скроет его (хотя он будет вызывать предупреждение компилятора или жалобу от некоторых помощников по рефакторингу, таких как ReSharper или CodeRush). Это компромиссные дизайнеры С#, которые встречаются между явными переопределениями С++ и неявными 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

Первый набор из пяти - это все, что нужно ожидать; потому что каждый уровень имеет реализацию и упоминается как объект того же типа, что и экземпляр, среда выполнения разрешает каждый вызов уровня наследования, на который ссылается тип переменной.

Второй набор из пяти - результат присвоения каждому экземпляру переменной ближайшего родительского типа. Теперь некоторые различия в поведении вытряхиваются; foo2, который фактически является Bar, отличным как Foo, все равно найдет более производный метод для панели фактического типа объекта. bar2 является Baz, но в отличие от foo2, поскольку Baz явно не переопределяет реализацию Bar (это не может, Bar sealed it), это не видно во время выполнения при поиске "сверху вниз" ", поэтому вместо этого вызывается реализация Bar. Обратите внимание, что Baz не нужно использовать ключевое слово new; вы получите предупреждение о компиляторе, если опустить ключевое слово, но подразумеваемое поведение на С# - это скрыть родительский метод. baz2 является Bai, который переопределяет реализацию Baz new, поэтому его поведение аналогично foo2 's; вызывается реализация фактического типа объекта в Бай. bai2 - это Bat, который снова скрывает реализацию родительского метода Bai, и он ведет себя так же, как bar2, хотя реализация Bai не запечатана, поэтому теоретически Bat мог бы переопределить, а не скрывать метод. Наконец, bat2 представляет собой Bak, который не имеет никакой переопределяющей реализации любого вида и просто использует его родительский.

Третий набор из пяти иллюстрирует полное поведение разрешения сверху вниз. Все на самом деле ссылается на экземпляр самого производного класса в цепочке Bak, но разрешение на каждом уровне типа переменной выполняется путем запуска на этом уровне цепочки наследования и сверления до самого производного явного переопределения метода, которые находятся в Bar, Bai и Bat. Метод, скрывающий, таким образом, "разрушает" основную цепочку наследования; вы должны работать с объектом на уровне или ниже уровня наследования, который скрывает метод для использования метода укрытия. В противном случае скрытый метод "раскрывается" и используется вместо этого.

Ответ 5

Пожалуйста, прочитайте о полиморфизме в С#: Полиморфизм (Руководство по программированию на С#)

Это пример оттуда:

Когда используется новое ключевое слово, вместо этого вызываются новые члены класса членов базового класса, которые были заменены. Этот базовый класс члены называются скрытыми членами. Скрытые члены класса все еще могут быть если экземпляр производного класса передается в экземпляр базовый класс. Например:

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

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

Ответ 6

Вам нужно сделать это virtual, а затем переопределить эту функцию в Teacher. Поскольку вы наследуете и используете базовый указатель для ссылки на производный класс, вам необходимо переопределить его с помощью virtual. new предназначен для скрытия метода класса base для ссылки на производный класс, а не для ссылки класса base.

Ответ 7

Я хотел бы добавить еще несколько примеров, чтобы расширить информацию об этом. Надеюсь, это тоже поможет:

Вот пример кода, который очищает воздух вокруг того, что происходит, когда производный тип присваивается базовому типу. Какие методы доступны, а также разница между переопределенными и скрытыми методами в этом контексте.

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-компилятор (intellisense) будет показывать a.foo() как A.foo().

Следовательно, ясно, что когда более производный тип присваивается базовому типу, переменная "базового типа" действует как базовый тип, пока не будет ссылаться на метод, который переопределен в производном типе. Это может стать немного противоречивым со скрытыми методами или методами с тем же именем (но не переопределенным) между родительским и дочерним типами.

Этот образец кода должен помочь очертить эти оговорки!

Ответ 8

С# отличается от java в поведении переопределения родительского/дочернего классов. По умолчанию в Java все методы являются виртуальными, поэтому поведение, которое вы хотите, поддерживается из коробки.

В С# вы должны пометить метод как виртуальный в базовом классе, тогда вы получите то, что хотите.

Ответ 9

Новое ключевое слово скажет, что метод в текущем классе будет работать, только если у вас есть экземпляр класса Teacher, хранящийся в переменной типа Teacher. Или вы можете вызвать его, используя отливки: ((Учитель) Лицо).ShowInfo()

Ответ 10

Тип переменной 'teacher' здесь typeof(Person), и этот тип ничего не знает о классе Учителя и не пытается искать какие-либо методы в производных типах. Чтобы вызвать метод класса Учителя, вы должны указать свою переменную: (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
{

}

Ответ 11

Компилятор делает это, потому что не знает, что это Teacher. Все, что он знает, это то, что это Person или что-то от этого. Поэтому все, что он может сделать, это вызвать метод Person.ShowInfo().

Ответ 12

Просто хотел дать краткий ответ -

Вы должны использовать virtual и override в классах, которые могут быть переопределены. Используйте virtual для методов, которые могут быть переопределены дочерними классами, и используйте override для методов, которые должны переопределять такие методы virtual.

Ответ 13

Я написал тот же код, что и u, упомянутый выше в java, за исключением некоторых изменений, и он отлично работал как исключенный. Метод базового класса переопределяется, поэтому выводимый вывод - "Я Учитель".

Причина:. Поскольку мы создаем ссылку базового класса (который может иметь ссылочный экземпляр производного класса), который фактически содержит ссылку производного класса. И поскольку мы знаем, что экземпляр всегда сначала смотрит на его методы, если обнаруживает, что он выполняет его, и если он не найдет определение там, оно будет расти в иерархии.

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");
    }
}

Ответ 14

Может быть, слишком поздно... Но вопрос прост, и ответ должен иметь тот же уровень сложности.

В вашей переменной кода человек ничего не знает о Teacher.ShowInfo(). Невозможно вызвать последний метод из ссылки базового класса, потому что он не виртуальный.

Существует полезный подход к наследованию - попробуйте представить, что вы хотите сказать с помощью своей иерархии кода. Также попробуйте представить, что делает тот или иной инструмент о себе. Например. если вы добавите виртуальную функцию в базовый класс, вы предположите: 1. он может иметь реализацию по умолчанию; 2. он может быть переопределен в производном классе. Если вы добавляете абстрактную функцию, это означает только одно: подкласс должен создать реализацию. Но если у вас есть простая функция - вы не ожидаете, что кто-то изменит ее реализацию.

Ответ 15

Основываясь на превосходной демонстрации 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());
    }
}

}

Здесь вывод:

person: Я Лицо == LinqConsoleApp.Teacher

учитель: я Учитель == LinqConsoleApp.Teacher

person1: Я Учитель == LinqConsoleApp.Teacher

person2: Я Учитель == LinqConsoleApp.Teacher

teacher1: Я Учитель == LinqConsoleApp.Teacher

person4: Я Лицо == LinqConsoleApp.Person

person3: Я являюсь интерфейсом Person == LinqConsoleApp.Person

Следует отметить две вещи:
Метод Teacher.ShowInfo() исключает новое ключевое слово. Когда новый опущен, поведение метода такое же, как если бы новое ключевое слово было явно определено.

Ключ переопределения можно использовать только в сочетании с виртуальным ключевым словом. Метод базового класса должен быть виртуальным. Или абстрактный, в этом случае класс также должен быть абстрактным.

человек получает базовую реализацию ShowInfo, потому что класс Учителя не может переопределить базовую реализацию (нет виртуальной декларации), а человек -.GetType(Учитель), поэтому он скрывает реализацию класса Учителя.

учитель получает производную реализацию Учителя ShowInfo, потому что учитель, потому что это Typeof (Учитель), и это не на уровне наследования Person.

person1 получает производную реализацию Teacher, потому что это .GetType(Учитель) и подразумеваемое новое ключевое слово скрывает базовую реализацию.

person2 также получает реализованную реализацию Teacher, даже несмотря на то, что она реализует IPerson и получает явное приведение к IPerson. Это опять же потому, что класс Учителя явно не реализует метод IPerson.ShowInfo().

teacher1 также получает производную реализацию Учителя, потому что это .GetType(Учитель).

Только person3 получает реализацию Showerson IPerson, поскольку только класс Person явно реализует метод, а person3 - экземпляр типа IPerson.

Чтобы явно реализовать интерфейс, вы должны объявить экземпляр var типа целевого интерфейса, и класс должен явно реализовать (полностью квалифицировать) членов интерфейса.

Обратите внимание, что даже человек4 не получает реализацию IPerson.ShowInfo. Это связано с тем, что хотя person4 является .GetType(Person), и хотя Person реализует IPerson, person4 не является экземпляром IPerson.

Ответ 16

Пример 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();
    }
}