it-swarm-eu.dev

Comment puis-je faire en sorte qu'un constructeur surchargé appelle à la fois le constructeur par défaut et une surcharge du constructeur de base?

Peut-être que la question que j'ai posée n'est pas la bonne question, car je sais déjà que la réponse courte est "vous ne pouvez pas".

La situation

J'ai une classe de base avec un constructeur surchargé qui prend deux arguments.

class Building
{
    public BuildingType BuildingType { get; protected set; }
    public string Address { get; set; }
    public decimal Price { get; set; }

    public Building()
    {
        BuildingType = BuildingType.General;
        Address = "Unknown";
    }

    public Building(string address, decimal price)
        : this()
    {
        Address = address;
        Price = price;
    }
}

La classe utilise une énumération

enum BuildingType { None, General, Office, Apartment }

Maintenant, je veux créer une classe enfant Office qui a également un constructeur surchargé. Cette classe enfant ajoute une autre propriété (Company). Dans cette classe, la propriété BuildingType doit naturellement être définie sur Office. Voici le code.

class Office : Building
{
    public string Company { get; set; }

    public Office()
    {
        BuildingType = BuildingType.Office;
    }

    public Office(string address, decimal price, string company)
        : base(address, price)
    {
        Company = company;
        // BuildingType = BuildingType.Office; // Don't wanna repeat statement
    }
}

Ce que je veux et pourquoi

Je veux que le deuxième constructeur de la classe Office exécute à la fois le constructeur base(address, price) ainsi que le constructeur par défaut de la classe Office. Je veux appeler le constructeur base(address, price) donc je n'ai pas à répéter l'attribution de toutes les propriétés de la classe de base. Je veux appeler le constructeur par défaut de la classe Office, car il définit la propriété BuildingType sur BuildingType.Office.

Maintenant, je sais que je ne peux pas utiliser quelque chose comme ça.

public Office(string address, decimal price, string company)
    : base(address, price) this()

Est-ce que je fais quelque chose de mal?

Je me demande s'il y a quelque chose qui ne va pas dans ma conception qui me donne envie d'appeler à la fois base (adresse, prix) et this (). Peut-être que je ne devrais pas définir le BuildingType dans le constructeur, mais ailleurs? J'ai essayé d'introduire un champ pour cela.

    public BuildingType BuildingType = BuildingType.General;

Mais je ne peux pas faire la même chose en classe enfant. Je cacherais le champ BuildingType dans la classe de base, je devrais donc utiliser l'opérateur new dans la classe enfant. J'ai essayé de faire le BuildingType dans la classe de base virtual, mais un champ ne peut pas être rendu virtuel.

Créer quelque chose dans le constructeur de base

Dans cet exemple simple, les constructeurs par défaut affectent uniquement des valeurs par défaut à certaines propriétés. Mais le constructeur du bâtiment pourrait également créer une fondation pour le bâtiment, tandis que le constructeur par défaut d'Office pourrait créer un ... (ne peut pas penser à quelque chose, mais vous avez l'idée). Donc, vous voudrez toujours exécuter les deux constructeurs par défaut.

Suis-je en train de penser dans la mauvaise direction?


Mise à jour

Basé sur la réponse et les commentaires de Jon Skeet, voici mon nouveau code. J'ai changé le chaînage des constructeurs du moins spécifique au plus spécifique. J'ai également ajouté le BuildingType au constructeur de la classe Building, rendu ce constructeur protégé et rendu le setter de propriété privé.

enum BuildingType { None, General, Office, Apartment }

class Building
{
    private const string DefaultAddress = "Unknown";

    public BuildingType BuildingType { get; private set; }
    public string Address { get; set; }
    public decimal Price { get; set; }

    #region Optional public constructors
    // Only needed if code other than subclass must 
    // be able to create a Building instance.
    // But in that case, the class itself can be abstract
    public Building() : this(DefaultAddress, 0m)
    {}

    public Building(string address, decimal price)
        : this(BuildingType.General, address, price) 
    {}
    #endregion

    protected Building(BuildingType buildingType)
        : this(buildingType, DefaultAddress, 0m) 
    {}

    protected Building(BuildingType buildingType,
        string address, decimal price)
    {
        BuildingType = buildingType;
        Address = address;
        Price = price;
    }
}

class Office : Building
{
    public string Company { get; set; }

    public Office() : this("Unknown Office", 0m, null) 
    {}

    public Office(string address, decimal price, string company)
        : base(BuildingType.Office, address, price)
    {
        Company = company;
    }
}

Pouvez-vous (Jon Skeet ou quelqu'un d'autre) commenter cette version révisée du code?

Un problème (mineur) qui n'est pas résolu par cela est que le constructeur par défaut pour la classe Office doit toujours fournir une adresse par défaut ("Unknown Office" Dans le code ci-dessus). Je préférerais toujours laisser le constructeur de la classe de base décider de l'adresse si elle n'est pas spécifiée. Donc, ce code ne fait toujours pas exactement ce que je veux.

Je pourrais probablement résoudre cela en n'utilisant pas de chaînage de constructeur dans la classe dérivée, mais au lieu de cela, chacun de ses constructeurs appelle directement le constructeur de base. Cela signifierait que je changerais le constructeur par défaut de la classe Office en

public Office() : base(BuildingType.Office)

Cela fonctionnerait pour cet exemple simple, mais s'il y a une méthode que j'aimerais exécuter à chaque instanciation d'un Office, je devrais appeler tous les constructeurs. C'est pourquoi le chaînage des constructeurs me semble une meilleure idée.

46
comecme

Votre approche n'est pas conventionnelle, ce qui résoudrait le problème. Au lieu de faire en sorte que le constructeur plus spécifique (celui avec beaucoup de paramètres) appelle celui sans paramètre, faites les choses dans l'autre sens - faites en sorte que l'un sans paramètre appelle l'autre, en fournissant les valeurs par défaut. Cela conduit généralement tous les constructeurs à en barrer un dans chaque classe à en appeler un "principal" (éventuellement indirectement via d'autres) et à ce que les appels de constructeur "principaux" fassent l'appel du constructeur de base.

class Office : Building
{
    public string Company { get; set; }

    public Office() : this(null, 0m, null)
    {
    }

    public Office(string address, decimal price, string company)
        : base(address, price)
    {
        Company = company;
        BuildingType = BuildingType.Office; // Don't wanna repeat statement
    }
}

... et la même chose dans la classe de base:

class Building
{
    public BuildingType BuildingType { get; protected set; }
    public string Address { get; set; }
    public decimal Price { get; set; }

    public Building() : this("Unknown", 0m)
    {
    }

    public Building(string address, decimal price)
    {
        BuildingType = BuildingType.General;
        Address = address;
        Price = price;
    }
}

(J'envisagerais sérieusement de faire en sorte que le constructeur Building inclue également un paramètre BuildingType.)

53
Jon Skeet

Créez une méthode privée qui attribue les valeurs par défaut à toutes vos propriétés et appelez-les séparément depuis chaque constructeur.

1
XtremeBytes