#c# #constructor
#c# #конструктор
Вопрос:
Я хочу иметь возможность автоматически вызывать определенный метод при создании производного объекта, однако я не могу придумать, как это сделать. Следующий код иллюстрирует. Другой ответ рекомендовал OnLoad, но я делаю это для Unity на Mac, и OnLoad, похоже, не поддерживается моей платформой. Есть предложения?
public class Parent {
public Parent ()
{
// A. Stuff to do before child constructor code runs
DoThisAutomaticallyAfterConstruction();
}
public void DoThisAutomaticallyAfterConstruction()
{
// C. In this example, this will run after A, before B. I want it to run ABC
}
}
public class Child : Parent {
public Child () : base()
{
// B. Stuff to do here after parent constructor code runs
}
}
Комментарии:
1. для меня это запах кода. базовый класс не должен иметь отношения к производным классам. кроме того, конструкторы должны настраивать инициализированное состояние объекта.
Ответ №1:
К сожалению, нет встроенного способа делать то, что вы хотите (это довольно часто запрашиваемая функция).
Одним из обходных путей является реализация фабричного шаблона, при котором вы не создаете объекты, вызывая конструктор напрямую, а вместо этого реализуете статический метод для их создания. для вас. Например:
public class MyClass
{
public MyClass()
{
// Don't call virtual methods here!
}
public virtual void Initialize()
{
// Do stuff -- but may be overridden by derived classes!
}
}
затем добавьте:
public static MyClass Create()
{
var result = new MyClass();
// Safe to call a virtual method here
result.Initialize();
// Now you can do any other post-constructor stuff
return resu<
}
и вместо того, чтобы делать
var test = new MyClass();
вы можете сделать
var test = MyClass.Create();
Комментарии:
1. Ах, блестяще! Я слышал о фабриках, но это помогает мне понять их гораздо лучше.
2. @RobinKing Смотрите в моем отредактированном ответе пару указателей на более подробную информацию о шаблоне factory. Статический фабричный метод — это простой подход, но некоторые предпочитают использовать фабричные объекты (и некоторые ситуации требуют их использования).
3. Это очень старый пост, но может быть полезно изменить конструктор на
private
, это заставит клиентов использовать фабрику.
Ответ №2:
Хотя (принятый) ответ @Jeremy Todd работает и является общепринятым решением проблемы, у него есть недостаток: он не очень удобен для IoC и сериализации, поскольку ваш класс не может быть правильно сконструирован с использованием new
. Позвольте мне представить общее решение, использующее некоторые функции C #. Обратите внимание, что это решение не требует от вас использования заводского шаблона или вызова чего-либо после построения объекта, и оно работает с любым классом, просто реализуя интерфейс с помощью одного метода. Сначала мы объявляем интерфейс, который должны будут реализовать наши классы:
public interface IInitialize {
void OnInitialize();
}
Затем мы добавим статический класс расширения для этого интерфейса и добавим метод Initialize:
public static class InitializeExtensions
{
public static void Initialize<T>(this T obj) where T: IInitialize
{
if (obj.GetType() == typeof(T))
obj.OnInitialize();
}
}
Теперь, если нам нужно, чтобы класс и все его потомки вызывали инициализатор сразу после того, как объект полностью сконструирован, все, что нам нужно сделать, это реализовать IInitialize
и добавить строку в конструктор:
public class Parent : IInitialize
{
public virtual void OnInitialize()
{
Console.WriteLine("Parent");
}
public Parent()
{
this.Initialize();
}
}
public class Child : Parent
{
public Child()
{
this.Initialize();
}
public override void OnInitialize()
{
Console.WriteLine("Child");
}
}
public class GrandChild : Child
{
public GrandChild()
{
this.Initialize();
}
public override void OnInitialize()
{
Console.WriteLine("GrandChild");
}
}
Хитрость заключается в том, что когда производный класс вызывает метод расширения Initialize
, это подавляет любые вызовы, не выполненные из фактического класса.
Ответ №3:
Это звучит как хороший кандидат для фабрики. Сделайте все конструкторы закрытыми или защищенными, требуя, чтобы потребители вашего кода вызывали фабричный метод, когда им нужен экземпляр вашего объекта. В фабричном методе вы используете new
оператор для создания объекта, а затем вызываете DoThisAutomaticallyAfterConstruction()
перед возвратом объекта.
Редактировать
Фабрика может быть статическим методом или у вас может быть объект factory. Смотрите, например, Википедию об абстрактном шаблоне фабрики по адресу http://en.wikipedia.org/wiki/Abstract_factory_pattern , и документация для ADO.NET DbProviderFactories в http://msdn.microsoft.com/en-us/library/wda6c36e.aspx для реализации в реальном мире.
Комментарии:
1. Имеет смысл. Спасибо!
Ответ №4:
Основываясь на вашем примере, вы выполняете ACB, вы хотите выполнить ABC.
Чтобы запустить код после дочернего конструктора, вам нужно выполнить вызов после B (дочернего конструктора). вы не можете вызвать код в A (родительском конструкторе), тогда вы не выполните ABC.
Переместить DoThisAutomaticallyAfterConstruction()
в конец конструктора дочернего класса?
Хотя действительно странный вопрос.
Комментарии:
1. К сожалению, это не обязательно помогает в незапечатанном классе, поскольку еще более производные классы могут выполнять дальнейшую инициализацию, которая будет выполняться после завершения выполнения вашего дочернего конструктора, поэтому вызывать виртуальные члены в конструкторе по-прежнему небезопасно.
2. @JeremyTodd — Нам понадобилось бы больше информации из OP, если бы это было так. Я отвечаю на информацию из этого сообщения.
3. В обычном коде это странно, но при работе с кодом пользовательского интерфейса это часто необходимо. Но, как сказал автор, обычно мы просто используем событие OnLoad .
4. @JeremyTodd, обратите внимание, что это не вызов виртуального метода, метод не является виртуальным. (Это может быть шаблон шаблонного метода, который может вызывать другие виртуальные методы, я согласен с этим.) Тем не менее, ваша большая точка зрения остается в силе, еще один внук производного класса не хотел бы, чтобы дочерний элемент запускал этот метод.
5. @AnthonyPegram — если это намерение OP, никаких упоминаний о других классах.