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

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

class A : B
{
    private int _classId;

    public A (int id) : base(id)
    {
        _classId = id;
    }

    public string GetString()
    {
        return "This will eventually be an important string.";
    }
}

class B
{
    private int _classId;

    public B (int id)
    {
        // here I want to be able to get access to class A's methods
        // such as GetString() ...
    }

    public void SomeMethod()
    {

    }
}

Итак, что я хочу сделать, это позволить В получить методы А.

Причина для этого? Я не хочу заполнять класс A, потому что у B будет много методов, поэтому было бы чётко хранить их в B, а затем просто получить их из A в другом коде. Я делаю это неправильно? разве это не то, что делают базовые классы? Я просто хочу иметь возможность делать это:

A.SomeMethodInB();

Не заполняя класс А мусором и делая его чище, храня его в классе В

4 ответа

Решение

Позвольте мне обобщить ваш вопрос:

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

Это оригинальное определение полиморфизма (в последнее время люди используют этот термин в более широком смысле), и оно не имеет ничего общего с ООП. Это также самая сложная область ООП для новичков.

Написать базовый класс с виртуальным методом

Давайте начнем с базового класса. Заметьте, я немного изменил ваш пример, по причине, которую я объясню через минуту.

class B
{
    virtual protected string GetString()
    {
        return "I am a B";
    }

    public void DoSomethingWithGetString()
    {
        Console.WriteLine(GetString());
    }
}

В этом примере DoSomethingWithGetString звонки GetString, Обратите внимание, что GetString является виртуальным Это говорит компилятору, что мы не хотим решать, где реализация GetString до времени выполнения. Это означает, что его нельзя связать во время компиляции: вместо этого его местоположение идентифицируется таблицей виртуальных методов во время выполнения, которая устанавливается при создании объекта. Если этот код выполняется как часть экземпляра класса B, он вызовет реализацию класса B GetString, Но если этот код был унаследован и фактически вызывается каким-либо другим объектом, который происходит от класса B, он будет вызывать версию этого класса GetString , Это называется поздним связыванием.

Напишите класс потомков

Итак, теперь давайте напишем класс A:

class A : B
{
    protected override GetString()
    {
        return "I am an A.";
    }
}

Поскольку A происходит от B, нам не нужно ничего реализовывать. Если мы позвоним A.DoSomethingWithGetString это на самом деле вызовет реализацию B, потому что это наследуется.

Выполнить его

Если вы запустите этот код

var a = new A();
a.DoSomethingWithGetString();  //Output = "I am an A"

... произойдет следующее:

  1. Экземпляр A, который происходит от B, создан
  2. VMT A настроен так, чтобы звонки GetString отправлены в A.GetString.
  3. Вы вызываете A.DoSomethingWithGetString
  4. Этот вызов направлен на базу кода B, потому что A не реализует его.
  5. Кодовая база в B обнаруживает, что GetString является виртуальным методом, и вызывает его, используя VMT.
  6. VMT указывает на реализацию GetString.
  7. A.GetString выполняется.

Таким образом, у вас есть ситуация, когда код в классе B вызывает код в классе A, о чем вы просили.

Вы также можете запустить его так:

B b = new A();
b.DoSomethingWithGetString(); //Output = "I am an A"

Таким образом, у вас есть указатель на B, который говорит, что это A, потому что он использует VMT, настроенный для класса A.

Но я не хочу никакой реализации в классе B

Теперь предположим, что вы не хотите, чтобы класс B вообще имел какую-либо реализацию для GetString, но вы все еще хотите иметь возможность вызывать реализацию в A. Есть способ сделать это с помощью абстрактного виртуального метода. Просто добавьте ключевое слово abstract и удалите реализацию:

abstract class B
{
    abstract protected string GetString(); //Notice no implementation

    public void DoSomethingWithGetString()
    {
        Console.WriteLine(GetString());
    }
}

Конечно, теперь вы не можете использовать класс B сам по себе, потому что для этого требуется реализация GetString, которая будет присутствовать только в классе-потомке. Таким образом, вы больше не можете создавать экземпляр B напрямую; Вы должны подтип, и использовать подтип.

Не могу использовать конструктор

Теперь вы заметите, что в моем примере я удалил ваши вызовы конструктора. Для этого есть причина. Конструктор никогда не должен вызывать метод, определенный в классе-потомке. Причина: во время построения конструкторы вызываются вверх и вниз по иерархии наследования, начиная с базового класса и спускаясь вниз по цепочке потомков. Это означает, что когда вы вызываете конструктор B, конструктор A еще не был вызван. если конструктор A еще не запущен, мы не можем быть уверены, что A был правильно настроен, и поэтому вызывать любой из его методов - действительно плохая идея.

Есть некоторые обходные пути для этого, но это тема для другого вопроса.

Если метод определен только в Class Aтогда нет возможности получить к нему доступ через Class B,

Если метод существует в обоих Class A а также Class Bтогда вы можете использовать virtual метод и override,

Думаю, что лучше использовать имена BaseClass и DerivedClass в качестве примера. Имена A и B являются произвольными. Хотя A предшествует B, имена не подразумевают предполагаемого направления наследования.

class Program
    {
        static void Main(string[] args)
        {
            var bc = new BaseClass(3);
            var dc = new DerivedClass(5);

            Console.WriteLine("Base Class Method A: " + bc.MethodA());
            Console.WriteLine("Derived Class Method A: " + dc.MethodA());
            Console.WriteLine("Base Class Method B: " + bc.MethodB());
            Console.WriteLine("Derived Class Method B: " + dc.MethodB());
            Console.ReadLine();
        }
    }

    public class BaseClass {
        protected int _classId;

        public BaseClass(int classId) {
            _classId = classId;
        }

        public virtual string MethodA() {
            return "Method A in base class: " + _classId.ToString();
        }

        public string MethodB()
        {
            return "I am a method B in the base class: " + _classId.ToString();
        }
    }

    public class DerivedClass : BaseClass {
        public DerivedClass(int classId)
            : base(classId)
        {

        }

        public override string MethodA() {
            return "Method A in derived class: " + _classId.ToString();
        }
    }

Есть два способа сделать это. Позвольте мне сначала привести пример.

Здесь у нас есть базовый класс, который содержит метод:

public class Base
{
    public void BaseMethod()
    {
        // Some method.
    }
}

Вот производный класс, где мы хотим вызвать метод:

public class DerivedClass : BaseClass
{
    // We want to use BaseMethod() here.
}

Чтобы вызвать метод из базового класса, мы используем base ключевое слово.

public class DerivedClass : BaseClass
{
    base.BaseMethod();
}

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

public class Base
{
    public virtual void BaseMethod()
    {
        // Some method.
    }
}

В производном классе теперь мы можем использовать ключевое слово override переопределить виртуальный метод базового класса, который переопределяет его для выполнения чего-то другого:

public class DerivedClass : BaseClass
{
    public override void BaseMethod()
    {
        // Some new function.
    }
}
Другие вопросы по тегам