c # вопрос переопределения наследования - PullRequest
0 голосов
/ 11 января 2010

Исправлены проблемы с кодом. Хорошо, я думаю, мне нужно уточнить вопрос.

new A.example (); выходы "А"

Что должно быть внутри метода примера, чтобы он мог выводить "???"? Это вообще возможно?

public class Letter {

    public virtual void AsAString() {
         Console.WriteLine("???");
    }
    public void example() {
         this.AsAString();
    }
}

public class A : Letter {
  public override void AsAString() { Console.WriteLine("A"); }
  public void example2() { base.AsAString(); }
}

new A().example2();
new A().example();

Ответы [ 6 ]

2 голосов
/ 11 января 2010

Давайте сначала убедимся, что я правильно понимаю ваш вопрос. У вас есть классы, определенные как выше. Вы создаете экземпляр A и вызываете метод example, который A наследует от базового класса. Вы хотите знать, возможно ли для вызова this.AsAString() в методе Letter.Example вызвать базовую реализацию AsAString, а не производную реализацию.

Во-первых, давайте разберемся, почему при example, указанном выше, вызов Letter.example через экземпляр A (например, new A().example) вызовет A.AsAString. Из спецификации (§7.4.4):

Определена реализация члена функции для вызова:

Если тип времени компиляции E является интерфейсом, то вызываемый элемент функции является реализацией M, предоставленной типом времени выполнения экземпляра, на который ссылается E. Этот член функции определяется путем применения правил отображения интерфейса. (§13.4.4), чтобы определить реализацию M, обеспеченную типом времени выполнения экземпляра, на который ссылается E.

В противном случае, , если M является членом виртуальной функции, вызываемый элемент функции является реализацией M, обеспечиваемого типом времени выполнения экземпляра, на который ссылается E. Этот член функции определяется путем применения правил для определение наиболее производной реализации (§10.6.3) M относительно типа времени выполнения экземпляра, на который ссылается E.

В противном случае M - это не виртуальный член функции, а вызываемый элемент функции - это сам M.

Итак, теперь давайте рассмотрим вашу ситуацию. У вас есть экземпляр a класса A, производный от Letter. Вы вызвали метод с именем example через синтаксис a.example(). Это вызовет Letter.example, который имеет определение:

public void example() {
    this.AsAString();
}

Это вызовет Letter.AsAString. Но Letter.AsAString объявляется virtual, и, следовательно, по приведенному выше полужирному правилу вызывается метод A.AsAString, поскольку this имеет тип A, A происходит от Letter, и A обеспечивает override из Letter.AsAString.

Теперь, если вы измените определение A.AsAString, чтобы оно скрывало базовую реализацию с помощью модификатора new

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

затем a.example приведет к использованию базовой реализации, и вы увидите вывод ??? по своему желанию. Это связано с тем, что по вышеприведенному правилу наиболее производная реализация Letter.AsAString (то есть наиболее производный тип в иерархии A, который предоставляет определение virtual метода AsAString) является базовой реализацией. Модификатор new позволяет A иметь метод с именем AsAString с той же сигнатурой, что и Letter.AsAString, но это не метод virtual.

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

2 голосов
/ 11 января 2010

Это очень просто:

public void example() {
     Console.WriteLine("???");
}

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

Если вы имеете в виду, что хотите вызвать виртуальный метод, как если бы он не был виртуальным, это невозможно. Даже если вы приведете ссылку на базовый класс, он все равно использует фактический тип объекта, чтобы определить, какой метод вызывать.

1 голос
/ 11 января 2010

Вы, кажется, немного озадачены тем, как работают виртуальные методы. Вот хороший способ подумать об этом. Представьте, что у каждого экземпляра каждого класса есть определенное количество «слотов». Во время выполнения слот содержит метод. Чтобы вызвать метод, вы указываете среде выполнения «вызовите любой метод, расположенный в слоте x этого объекта».

Когда вы создаете «абстрактный» метод, он создает новый слот и ничего не помещает в него.

Когда вы создаете «виртуальный» метод, он создает новый слот и помещает метод в слот.

Когда вы создаете метод переопределения, он не создает новый слот. Он «переопределяет», заменяя все, что находится в ранее объявленном слоте виртуального метода.

Когда вы делаете «новый» метод, он делает новый слот. Вот что значит «новый».

Когда вы создаете обычный метод без аннотации, он действует как «новый» метод.

Когда вы пишете код, который вызывает метод, компилятор определяет, о каком слоте вы говорите, и генерирует код, который сообщает среде выполнения «вызовите любой метод, который окажется в этом слоте этого объекта».

Исключением из этого правила является "базовый" вызов; он генерирует код, который означает «игнорировать то, что находится в слоте, и вызвать версию этого метода для базового класса».

Теперь понятно?

1 голос
/ 11 января 2010

Ключевое слово virtual в C # ведет себя так же, как и в большинстве других языков - в частности, конкретный вызываемый метод определяется фактическим типом времени выполнения экземпляра.

Кажется, что вы ищете просто метод, скрывающий . Программа:

class A
{
    public void Foo()
    {
        Console.WriteLine("Foo called from A");
    }

    public virtual void Bar()
    {
        Console.WriteLine("Bar called from A");
    }

    public virtual void Baz()
    {
        Console.WriteLine("Baz called from A");
    }
}

class B : A
{
    public new void Foo()
    {
        Console.WriteLine("Foo called from B");
    }

    public override void Bar()
    {
        Console.WriteLine("Bar called from B");
    }

    public override void Baz()
    {
        base.Baz();
        Console.WriteLine("Baz called from B");
    }
}

static void Main()
{
    A a = new A();
    a.Foo();
    a.Bar();
    a.Baz();

    B b = new B();
    b.Foo();
    b.Bar();
    b.Baz();

    A a2 = new B();
    a2.Foo();
    a2.Bar();
    a2.Baz();
}

Будет выдавать следующий вывод:

Foo called from A
Bar called from A
Baz called from A

Foo called from B
Bar called from B
Baz called from A
Baz called from B

Foo called from A
Bar called from B
Baz called from A
Baz called from B

Давайте разберемся с этим:

  • Метод A.Foo() - это не виртуальный метод, который скрыт на B. Если вы вызовете Foo для переменной, объявленной как A, вы всегда будете вызывать A.Foo(), даже если это на самом деле экземпляр B.

  • Метод A.Bar() - это виртуальный метод, который переопределяется на B. Если вы вызовете Bar для переменной, объявленной как A, вы фактически вызовете B.Foo(), если переменная действительно является экземпляром B. Так работает виртуальная диспетчеризация, в соответствии со спецификацией

  • Метод A.Baz() также является виртуальным, но B.Baz() также вызывает базовую версию перед запуском собственного кода. Вот почему вы видите две строки вывода этого метода в последних двух наборах. Только виртуальный базовый метод может вызывать - нет способа вызвать его извне.

Таким образом, если вам нужно выполнить как базовый, так и производный методы, не делает метод виртуальным. Скрыть или shadow в производном классе.

1 голос
/ 11 января 2010

Отвечая на ваш вопрос, я не думаю, что это возможно. Как только вы переопределите метод, если только вы не используете базовый метод доступа для доступа к реализации базового класса, ваш вызов будет к «наиболее специализированному» методу, который у вас есть (в этом случае метод в AsASString определен в A, а не в AsASString, определенный в письмо.

Предложение, что Woot4Moo, собранный вместе, не будет работать, так как AsASString () и this.AsASString () обращаются к одному и тому же методу, IMHO (реализация этого метода в A).

Как вы узнали, base.AsASString () фактически вызывает базовый метод, а не специализированный метод.

Надеюсь, это поможет.

Ура, Вагнер.

0 голосов
/ 11 января 2010

Решением этого является объявление A.AsAString следующим образом:

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

Это может не соответствовать вашим потребностям, так как ((Letter)new A()).AsAString() вернет "???". Если это соответствует вашим потребностям, то вы можете не объявлять его виртуальным.

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

Еще один способ думать об этом состоит в том, что такое решение, если оно существует, довольно сильно нарушит инкапсуляцию. Класс должен быть авторитет о том, как он взаимодействует с базовым классом. Для пользователей класса точная реализация вызываемой базовой функции должна быть непрозрачной. A.AsAString() должен быть авторитетом в его взаимодействии с Letter.AsAString(), а не с кем-либо за его пределами.

Чтобы реализовать то, что вы хотите, вам нужно добавить еще одну функцию, например: публичное письмо класса {

    public virtual void AsAString() {
        BaseAsAString();
    }
    public virtual void BaseAsAString() {
        Console.WriteLine("???");
    }
    public void example() {
        this.AsAString();
    }
}

public class A : Letter {
    public override void AsAString() { Console.WriteLine("A"); }
}

A a = new A();
a.AsAString(); //A
a.BaseAsAString(); //???
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...