3
votes

Comment faire exécuter le constructeur de la classe héritée après le constructeur d'une classe

J'ai 2 classes: Base et Main . Quand j'exécute le constructeur de Main , je veux exécuter du code AVANT que le constructeur de Base s'exécute. En java , c'est facile, vous écrivez du code puis utilisez super . Existe-t-il un moyen d'obtenir le même résultat en utilisant 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);
}

Je m'attends à ce que la sortie soit Hello world , mais ce n'est que Bonjour , car le constructeur de Main s'exécute après le constructeur de Base .


3 commentaires

Non, il n'y a aucun moyen de modifier la commande, vous aurez besoin d'une autre solution. Cependant, je n'ai jamais eu besoin de quelque chose comme ça, c'est vraiment étrange.


peut-être que la composition serait meilleure dans ce cas


Non, il n'y a pas de moyen facile. Utilisez le modèle de fabrique pour construire, puis appelez une méthode «PostInitialize» ou similaire sur l'instance avant de la renvoyer à partir de la méthode de fabrique.


5 Réponses :


4
votes

Il n'y a pas de moyen simple. Solution: déclarez la méthode protected abstract void PreInitialization () et appelez-la DÈS QUE POSSIBLE dans le ctor de Base . Ensuite, fournissez une implémentation appropriée au niveau Principal . C'est le plus proche de vos besoins.

Autre (meilleure) solution: fournir une Factory autonome où T: Base avec T Make (Action preInitializationAction); . Ensuite, vous décidez quand créer un objet et quand invoquer le rappel fourni; mais malgré tout, il peut y avoir des problèmes pour accéder aux champs de T : votre rappel ne pourra pas y accéder car l'objet n'existe pas encore.

P.S. On dirait que vous avez mal conçu quelque chose. Une telle solution est assez peu fiable et ne suit pas les pratiques connues pour être bonnes.


2 commentaires

L'appel d'une méthode virtuelle dans un constructeur est une recette pour les problèmes, car il s'exécutera dans la version la plus surchargée de la méthode, avant que le constructeur du type qui a défini cette méthode ne se soit exécuté. Ce n'est pas recommandé.


Je suis d'accord. C'est pourquoi j'ai ajouté "P.S." section en bas.



1
votes

Je ne pense pas que ce soit possible et si c'était le cas, vous ne pourriez rien faire avec la classe de base car elle serait toujours nulle jusqu'à ce que le constructeur s'exécute. Ce que vous pouvez faire est de laisser le constructeur vide et de créer un 'Construct' vide et de l'appeler à partir de la fin du constructeur de Main, de cette façon vous pourrez changer myString car l'objet existe.


0 commentaires

1
votes

Il n'y a aucun moyen légal d'y parvenir par quelque construction de langage que ce soit. Ce qui vient à l'esprit est de simuler ce comportement en utilisant un constructeur dans la classe de base qui accepterait une action à exécuter en premier:

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

Quoi qu'il en soit, vous ne savez pas pourquoi vous en avez besoin, mais veuillez noter qu'une telle implémentation les exigences peuvent indiquer un problème de conception.


0 commentaires

1
votes

Ce n'est qu'une démonstration. Ne l'utilisez pas en production.

Vous demandez quelque chose d'anormal pour c #. Et j'aime ça. Préparez-vous à regarder de la magie. Mais commençons par patcher le code initial. Tout d'abord, changeons le nom de la classe Main en Derived. Il est également connu que l'initialisation en ligne sur le terrain est un sucre sintactique. Le compilateur met l'initialisation à tous les constructeurs. Nous aussi. Et ajoutons une fusion nulle. Cela n'affectera pas le résultat au début, mais sera ensuite une aide précieuse. Et nous devons également placer nos classes dans une bibliothèque de classes distincte (par exemple, ClassLibrary). Nous avons donc:

public class Derived : Base
{
    public Derived()
    {
        myString = "Hello";
        base..ctor(" world");
    }
}

Ensuite, compilez la bibliothèque, créez une application console et ajoutez une référence à la dll compilée via Ajouter une référence ... -> Parcourir -> Parcourir .. . Ajoutez du code comme le vôtre:

"%ILASM_LOCATION%\ilasm.exe" "%ClassLibrary.il_LOCATION%\ClassLibrary.il" /dll /output:"%ClassLibrary.dll_LOCATION%\ClassLibrary.dll"

La sortie est juste Bonjour comme dans votre cas.

Maintenant, ouvrez la dll avec ildasm et videz-la (File -> Dump, Dump IL Code vérifié) dans ClassLibrary.il et quittez ildasm. Ouvrez ClassLibrary.il avec n'importe quel éditeur de texte et recherchez le constructeur de classe dérivée. Il commence par .class public auto ansi beforefieldinit ClassLibrary.Derived et contient:

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

Changez-le en (dans IL, on appelle manuellement le constructeur de classe de base n'importe où ):

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

Enregistrez ensuite ClassLibrary.il et exécutez l'invite de commande:

using System;
using ClassLibrary;

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

Exécutez à nouveau l'application console et voyez : Bonjour tout le monde

Ouvert avec les looks de classe dérivés ILSpy:

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

Mais mis au projet ClassLibrary cela donne:

Erreur CS1001 Identifier attendu ...

Erreur CS7036 Il n'y a pas d'argument donné qui correspond au paramètre formel requis 'str' de 'Base.Base (string)' ...

L'erreur CS0117 'Base' ne contient pas de définition pour '' ...


0 commentaires

0
votes

Ce n'est qu'une démonstration. Ne l'utilisez pas en production.

Modifiez un peu Base pour des raisons techniques.

        Tracing: ctor 'Base(string)' called
Hi from base
        Tracing: ctor 'Main()' called
        Tracing: ctor 'Base(string)' called
Hello world

Modifiez Principal .

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(new Base(" from base").myString);
        Console.WriteLine(new Main().myString);
    }
}

Maintenant, testez-le.

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

Donne:

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;
    }
}


0 commentaires