#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