Wysyłanie referencji obiektu przed jego budową

Widziałem następujący kod w jednej z naszych aplikacji:

public class First()
{
      private Second _second;

      public First()
      {
          _second = new Second(this);
          // Doing some other initialization stuff,
      }

}

public class Second
{
    public Second(First f)
    {
    }
}

W konstruktorze First(), czy nie jest źle, że wysyłamy referencję klasy First() przed jest w pełni skonstruowany? Myślę, że obiekt jest w pełni skonstruowany dopiero wtedy, gdy logika sterowania opuści konstruktor.

Czy to w porządku?
Author: hmmftg, 2012-08-03

8 answers

Moje pytanie brzmi, czy w konstruktorze First() nie jest źle, że wysyłamy referencję klasy First (), zanim zostanie ona w pełni skonstruowana?

Trochę. To może być problem, oczywiście.

Jeśli Second Konstruktor po prostu trzyma referencję do późniejszego użycia, to nie jest tak źle. Jeśli natomiast konstruktor Second wywoła z powrotem do First:

public Second(First f)
{
    f.DoSomethingUsingState();
}

... a Państwo jeszcze nie zostało utworzone, to oczywiście byłoby to bardzo Zła Rzecz. Jeśli wywołasz metodę virtual na First, to może być jeszcze gorzej - możesz skończyć wywołaniem jakiegoś kodu, który nie miał jeszcze okazji uruchomić żadnego jej konstruktora (chociaż jego inicjalizatory zmiennych zostaną uruchomione).

W szczególności, readonly pola mogą być wyświetlane najpierw z jedną wartością, a później z inną...

I blogowałem o tym jakiś czas temu, co może dostarczyć więcej informacji.

Oczywiście, BEZ robienia tego typu rzeczy, trudno jest stworzyć dwa wzajemnie odwołujące się niezmienne obiekty...

 70
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-23 15:24:34

Jeśli napotkasz ten wzór, możesz sprawdzić, czy można go przekształcić w ten:

public class First()
{
      private Second _second;

      public First()
      {
          _second = new Second(this);
          // Doing some other initialization stuff,
      }

      private class Second
      {
          public Second(First f)
          {
          }
      }
}

Przekazanie zależności do konstruktora implikuje pewien rodzaj ścisłego sprzężenia między dwiema klasami, ponieważ pierwsza musi zaufać drugiej, która wie, co robi i nie będzie próbowała polegać na niezainicjalizowanym stanie First. Ten rodzaj silnego sprzężenia jest bardziej odpowiedni, gdy Second jest prywatną zagnieżdżoną podklasą (a tym samym wyraźnym szczegółem implementacji) lub ewentualnie, gdy jest Klasa wewnętrzna.

 17
Author: Dan Bryant,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-02 20:09:24

Odpowiedź brzmi: to zależy. Ogólnie rzecz biorąc, byłoby to uważane za zły pomysł ze względu na potencjalne konsekwencje.

Mówiąc dokładniej, dopóki Second nie używa niczego z First przed jego zbudowaniem, powinieneś być w porządku. Jeśli jednak nie możesz tego jakoś zagwarantować, zdecydowanie możesz napotkać problemy.

 11
Author: Justin Niessner,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-02 20:05:15

Tak, jest trochę źle. Możliwe jest robienie rzeczy z polami First, zanim zostaną w pełni zainicjalizowane, co spowodowałoby niepożądane lub nieokreślone zachowanie.

To samo dzieje się, gdy wywołujesz metodę wirtualną z konstruktora.

 4
Author: Daniel Pelsmaeker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-02 20:05:19

W przeciwieństwie do np. C++, CLR nie ma pojęcia w pełni skonstruowanych lub niekompletnie skonstruowanych obiektów. Gdy tylko alokator pamięci zwróci zerowany obiekt i zanim konstruktor uruchomi się, jest on gotowy do użycia (z punktu widzenia CLR). Ma swój ostateczny Typ, wywołania wirtualnych metod wywołują najbardziej pochodne nadpisanie itd. Możesz użyć this w ciele konstruktora, wywołać metody wirtualne itp. Może to rzeczywiście powodować problemy z kolejnością inicjalizacji, ale nie ma nic w CLR do zapobiegaj im.

 3
Author: Anton Tykhyy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-02 22:37:11

To prawda, że może to prowadzić do problemów, jak to opisałeś. Dlatego generalnie zaleca się uruchamianie poleceń takich jak _second = new Second(this); tylko po innych rzeczach inicjalizacyjnych sugerowanych przez twój komentarz.

Dość często ten wzorzec jest jedynym rozwiązaniem do przechowywania wzajemnych odniesień między dwoma obiektami. W wielu przypadkach dzieje się to jednak w taki sposób, że Klasa otrzymująca potencjalnie-Nie-w pełni zainicjalizowaną instancję jest ściśle powiązana z klasą, do której się odwołuje (np. ten sam autor; część tej samej aplikacji; lub klasa zagnieżdżona, ewentualnie prywatna). W takich przypadkach można uniknąć negatywnych skutków, ponieważ autor Second zna (lub może nawet napisał) wewnętrzne strony First.

 1
Author: O. R. Mapper,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-02 20:07:08

To zależy od scenariusza, ale może prowadzić do trudnych do przewidzenia zachowań. Jeśli Second zrobi cokolwiek z First w konstruktorze, to zachowanie może stać się źle zdefiniowane po zmianie konstruktora First. Dodatkowe wskazówki konstruktora sugerują również, że nie należy wywoływać wirtualnych lub abstrakcyjnych metod (na konstruowanej klasie) w konstruktorze, ponieważ może to prowadzić do podobnych konsekwencji, w których zachowanie może być trudne do rozumowania.

 1
Author: eulerfx,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-02 20:10:03

Odpowiedź na to pytanie zależy od charakteru relacji pomiędzy First i Second.

Zastanów się, jakiego rodzaju obiekt może się składać z innego obiektu, który sam się składa (lub wymaga do jego inicjalizacji) obiektu typu First. W takich sytuacjach należy uważać na tworzenie wykresów obiektowych z cyklami.

Niemniej jednak istnieje wiele uzasadnionych sytuacji, w których cykl powinien wystąpić w grafie obiektowym. Jeśli First opiera się na stanie Second, aby wykonać jego inicjalizację, powinieneś zachować metodę taką, jaka jest i to jest ogólnie w porządku. Jeśli Second opiera się na stanie First, Aby wykonać własną inicjalizację, powinieneś prawdopodobnie zmienić konstruktor w taki sposób:

  public First()
  {

      // Doing some other initialization stuff,
      _second = new Second(this);
  }

Jeśli oba poprzednie stwierdzenia są prawdziwe (Second zależy od stanu First, A First zależy od stanu Second), to prawie na pewno powinieneś wrócić do swojego projektu i dowiedzieć się dokładniej charakter relacji pomiędzy First i Second. (Może powinien istnieć jakiś obiekt Third, który zawiera odniesienie do First i Second, a związek między tymi dwoma ostatnimi powinien być rozstrzygany przez Third.)

 1
Author: Michael Graczyk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-02 20:13:38