Как заставить конструктор унаследованного класса работать после конструктора класса - PullRequest
2 голосов
/ 13 апреля 2019

У меня есть 2 класса: Base и Main. Когда я запускаю конструктор Main, я хочу запустить некоторый код ДО запуска конструктора Base. В 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.

Ответы [ 4 ]

4 голосов
/ 13 апреля 2019

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

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

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

1 голос
/ 14 апреля 2019

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

Вы просите что-то неестественное для 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 с помощью Добавить ссылку ... -> Обзор -> Обзор ... Добавить код, подобный вашему:

using System;
using ClassLibrary;

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

Вывод просто Hello, как в вашем случае.

Теперь откройте dll с помощью ildasm и сбросьте его (File -> Dump, Dump IL Code проверено) в ClassLibrary.иль и выход 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он дает:

Ошибка CS1001 Ожидается идентификатор ...

Ошибка CS7036 Не указан аргумент, который соответствует необходимому формальному параметру 'str' из Base.Base (строка)'...

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

Обновление :

Магияотражения:

using System;
using System.Reflection;

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

    public static class TraceHelper
    {
        public static void Trace(string message)
        {
            var color = Console.ForegroundColor;
            Console.ForegroundColor = ConsoleColor.Yellow;
            Console.WriteLine($"Trace: {message}.");
            Console.ForegroundColor = color;
        }
    }

    public class Base
    {
        public string myString;

        public Base(string str)
        {
            TraceHelper.Trace("Base constructor called");

            myString = myString ?? "Hi";
            myString += str;
        }
    }

    public class Derived : Base
    {
        private static readonly ConstructorInfo baseCtor = typeof(Base).GetConstructor(new[] { typeof(string) });

        public Derived() : base(null)
        {
            TraceHelper.Trace("Derived constructor called");

            base.myString = "Hello";
            @base(" world");
        }

        private void @base(string str)
        {
            baseCtor.Invoke(this, new[] { str });
        }
    }
}

Вывод:

Trace: Base constructor called.
Trace: Derived constructor called.
Trace: Base constructor called.
Hello world

Результат "Hello world" , но базовый конструктор вызывается дважды .

1 голос
/ 14 апреля 2019

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

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

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

1 голос
/ 13 апреля 2019

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...