Как заставить конструктор унаследованного класса запускаться после конструктора класса

#c# #inheritance #constructor

#c# #наследование #конструктор

Вопрос:

У меня есть 2 класса: Base и Main . Когда я запускаю конструктор Main s, я хочу запустить некоторый код ПЕРЕД Base запуском конструктора s. В java , это просто, вы пишете некоторый код, а затем используете super . Есть ли способ достичь того же результата с помощью C# ?

 class Base
{
    public string myString = "Hi";
    public Base(string str)
    {
        myString  = str;
    }
}

class Main : Base
{
    public Main() : base(" world")
    {
        base.myString = "Hello";
    }
}

class Program
{
    Console.WriteLine(new Main().myString);
}
 

Я ожидаю, что результат будет Hello world , но это только Hello Main потому, что конструктор запускается после конструктора Base .

Комментарии:

1. Нет, изменить порядок невозможно, вам понадобится другое решение. Однако мне никогда не было нужно ничего подобного, это действительно необычно.

2. может быть, в этом случае композиция была бы лучше

3. Нет, простого способа нет. Используйте фабричный шаблон для создания, а затем вызовите «PostInitialize» или аналогичный метод для экземпляра, прежде чем возвращать его из фабричного метода.

Ответ №1:

Простого способа нет. Решение: объявите protected abstract void PreInitialization() метод и вызовите его как можно скорее в Base ctor. Затем предоставьте соответствующую реализацию на Main уровне. Это наиболее близко к вашему требованию.

Другое (лучшее) решение: предоставить автономный Factory<T> where T : Base с. T Make(Action preInitializationAction); Затем вы решаете, когда создавать объект и когда вызывать предоставленный обратный вызов; но все же могут возникнуть проблемы с доступом T к полям: ваш обратный вызов не сможет получить к ним доступ, поскольку объект еще не существует.

PS Похоже, вы что-то неправильно спроектировали. Такое решение довольно ненадежно и не соответствует практике, которая, как известно, хороша.

Комментарии:

1. Вызов виртуального метода в конструкторе — это путь к проблемам, поскольку он будет выполняться в наиболее переопределенной версии метода до выполнения конструктора для типа, который определил этот метод. Это не рекомендуется.

2. Я согласен. Вот почему я добавил раздел «PS» внизу.

Ответ №2:

Я не думаю, что это возможно, и если бы это было так, вы не смогли бы ничего сделать с базовым классом, потому что он все равно был бы нулевым, пока не запустится конструктор. Что вы можете сделать, это оставить конструктор пустым и создать пустую «конструкцию» и вызвать ее из конца конструктора Main, таким образом, вы сможете изменить myString, потому что объект существует.

Ответ №3:

Законного способа добиться этого с помощью какой-либо языковой конструкции не существует. Что приходит на ум, так это имитировать это поведение с использованием конструктора в базовом классе, который сначала выполнял бы какое-либо действие:

 class Base
{
    public string myString = "Hi";
    public Base(string str)
    {
        myString  = str;
    }

    public Base(string str, Action<Base> runFirts)
    {
        runFirts?.Invoke(this);
        myString  = str;
    }
}

class Main : Base
{
    public Main() : base(
        "world",
        instance => { instance.myString = "Hello"; })
    {
    }
}
 

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

Ответ №4:

Отказ от ответственности

Это просто демонстрация. Не используйте его в производстве.

Вы просите что-то неестественное для c #. И мне это нравится. Приготовьтесь наблюдать за волшебством. Но давайте начнем с исправления исходного кода. Сначала давайте изменим имя основного класса на производное. Также известно, что встроенная инициализация поля является синтактическим сахаром. Компилятор помещает инициализацию во все конструкторы. Мы тоже. И давайте добавим объединение нулей. Сначала это не повлияет на результат, но в дальнейшем будет дополнительной помощью. И мы также должны поместить наши классы в отдельную библиотеку классов (например, ClassLibrary). Итак, мы имеем:

 namespace ClassLibrary
{
    public class Base
    {
        public string myString;

        public Base(string str)
        {
            myString = myString ?? "Hi";
            myString  = str;
        }
    }

    public class Derived : Base
    {
        public Derived() : base(" world")
        {
            base.myString = "Hello";
        }
    }
}
 

Затем скомпилируйте библиотеку, создайте консольное приложение и добавьте ссылку на скомпилированную dll с помощью Add Reference … -> Обзор -> Обзор…
Добавьте код, подобный вашему:

 using System;
using ClassLibrary;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(new Derived().myString);
        }
    }
}
 

Результат — это просто
Hello
как и в вашем случае.

Теперь откройте dll с помощью ildasm и сбросьте ее (File -> Dump, дамп IL-кода проверен) в ClassLibrary.il и выйдите из ildasm. Открыть ClassLibrary.il с помощью любого текстового редактора и найдите конструктор производного класса. Он начинается с .class public auto ansi beforefieldinit ClassLibrary.Derived и содержит:

 IL_0000:  ldarg.0
IL_0001:  ldstr      " world"
IL_0006:  call       instance void ClassLibrary.Base::.ctor(string)
IL_000b:  nop
IL_000c:  nop
IL_000d:  ldarg.0
IL_000e:  ldstr      "Hello"
IL_0013:  stfld      string ClassLibrary.Base::myString
IL_0018:  ret
 

Измените его на (в IL один вызывает конструктор базового класса в любом месте вручную):

 IL_0000:  ldarg.0
IL_0001:  ldstr      "Hello"
IL_0006:  stfld      string ClassLibrary.Base::myString
IL_000b:  nop
IL_000c:  nop
IL_000d:  ldarg.0
IL_000e:  ldstr      " world"
IL_0013:  call       instance void ClassLibrary.Base::.ctor(string)
IL_0018:  ret
 

Затем сохраните ClassLibrary.il и из командной строки запустите:

 "%ILASM_LOCATION%ilasm.exe" "%ClassLibrary.il_LOCATION%ClassLibrary.il" /dll /output:"%ClassLibrary.dll_LOCATION%ClassLibrary.dll"
 

Запустите консольное приложение еще раз и посмотрите:
Hello world

Открытый с помощью ILSpy Производный класс выглядит:

 public class Derived : Base
{
    public Derived()
    {
        myString = "Hello";
        base..ctor(" world");
    }
}
 

Но, помещенный в ClassLibrary project, он дает:

Ожидаемый идентификатор ошибки CS1001…

Ошибка CS7036 Не указан аргумент, соответствующий требуемому формальному параметру ‘str’ из ‘Base.Base(string)’…

Ошибка CS0117 «База» не содержит определения для «…

Ответ №5:

Отказ от ответственности

Это просто демонстрация. Не используйте его в производстве.

Base Немного доработать по техническим причинам.

 class Base
{
    public string myString;

    protected Base(object obj) { }

    public Base(string str)
    {
        Console.WriteLine("tTracing: ctor 'Base(string)' called");
        myString = myString ?? "Hi";
        myString  = str;
    }
}
 

Изменить Main .

 class Main : Base
{
    private Action<string> ctor;

    private Action<string> @base =>
        ctor ?? (ctor = (Action<string>)Delegate.CreateDelegate(typeof(Action<string>), this as Base, ".ctor"));

    public Main() : base(new object())
    {
        Console.WriteLine("tTracing: ctor 'Main()' called");
        myString = "Hello";
        @base(" world");
    }
}
 

Теперь протестируйте его.

 class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(new Base(" from base").myString);
        Console.WriteLine(new Main().myString);
    }
}
 

Дает:

         Tracing: ctor 'Base(string)' called
Hi from base
        Tracing: ctor 'Main()' called
        Tracing: ctor 'Base(string)' called
Hello world