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

#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, никаких упоминаний о других классах.