Comment hériter des constructeurs?

Imaginez une classe de base avec plusieurs constructeurs et une méthode virtuelle

public class Foo { ... public Foo() {...} public Foo(int i) {...} ... public virtual void SomethingElse() {...} ... } 

et maintenant je veux créer une classe descendante qui remplace la méthode virtuelle:

 public class Bar : Foo { public override void SomethingElse() {...} } 

Et un autre descendant qui fait plus de choses:

 public class Bah : Bar { public void DoMoreStuff() {...} } 

Dois-je vraiment copier tous les constructeurs de Foo dans Bar et Bah? Et puis, si je change une signature de constructeur dans Foo, dois-je la mettre à jour dans Bar and Bah?

N’y a-t-il aucun moyen d’hériter des constructeurs? N’y a-t-il aucun moyen d’encourager la réutilisation du code?

Oui, vous devrez implémenter les constructeurs qui ont un sens pour chaque dérivation, puis utiliser le mot clé base pour diriger ce constructeur vers la classe de base appropriée ou le mot this clé this pour diriger un constructeur vers un autre constructeur de la même classe.

Si le compilateur faisait des suppositions sur l’inheritance de constructeurs, nous ne pourrions pas déterminer correctement comment nos objects étaient instanciés. Dans la plupart des cas, vous devriez vous demander pourquoi vous avez tant de constructeurs et envisager de les réduire à un ou deux seulement dans la classe de base. Les classes dérivées peuvent alors en masquer certaines en utilisant des valeurs constantes telles que null et exposer uniquement les valeurs nécessaires à travers leurs constructeurs.

Mettre à jour

Dans C # 4, vous pouvez spécifier des valeurs de paramètre par défaut et utiliser des parameters nommés pour qu’un seul constructeur prenne en charge plusieurs configurations d’argument plutôt qu’un seul constructeur par configuration.

387 constructeurs ?? C’est votre problème principal. Que diriez-vous de cela à la place?

 public Foo(params int[] list) {...} 

Oui, vous devez copier tous les 387 constructeurs. Vous pouvez réutiliser en les redirigeant:

  public Bar(int i): base(i) {} public Bar(int i, int j) : base(i, j) {} 

mais c’est le mieux que vous puissiez faire.

Dommage que nous soyons obligés de dire au compilateur l’évidence:

 Subclass(): base() {} Subclass(int x): base(x) {} Subclass(int x,y): base(x,y) {} 

Je n’ai besoin que de faire 3 constructeurs dans 12 sous-classes, donc ce n’est pas grave, mais je n’aime pas trop répéter cela sur chaque sous-classe, après avoir été habitué à ne pas avoir à l’écrire aussi longtemps. Je suis sûr qu’il y a une raison valable à cela, mais je ne pense pas avoir déjà rencontré un problème nécessitant ce type de ressortingction.

N’oubliez pas que vous pouvez également redirect les constructeurs vers d’autres constructeurs au même niveau d’inheritance:

 public Bar(int i, int j) : this(i) { ... } ^^^^^ 

Une autre solution simple pourrait consister à utiliser une structure ou une classe de données simple contenant les parameters en tant que propriétés. De cette façon, vous pouvez configurer toutes les valeurs et tous les comportements par défaut à l’avance, en passant la “classe de parameters” en tant que paramètre de constructeur unique:

 public class FooParams { public int Size... protected myCustomStruct _ReasonForLife ... } public class Foo { private FooParams _myParams; public Foo(FooParams myParams) { _myParams = myParams; } } 

Cela évite le gâchis des constructeurs multiples (parfois) et donne un typage fort, des valeurs par défaut et d’autres avantages non fournis par un tableau de parameters. Il facilite également le transfert car tout ce qui hérite de Foo peut toujours atteindre ou même append à FooParams selon les besoins. Vous devez toujours copier le constructeur, mais vous avez toujours (la plupart du temps) seulement (en règle générale) uniquement (du moins pour le moment) un seul constructeur.

 public class Bar : Foo { public Bar(FooParams myParams) : base(myParams) {} } 

J’aime beaucoup les approches surchargées d’Initailize () et de Class Factory, mais il suffit parfois d’avoir un constructeur intelligent. Juste une pensée.

Comme Foo est une classe, ne pouvez-vous pas créer des méthodes Initialise() surchargées virtuelles? Ensuite, ils seraient disponibles pour les sous-classes et encore extensibles?

 public class Foo { ... public Foo() {...} public virtual void Initialise(int i) {...} public virtual void Initialise(int i, int i) {...} public virtual void Initialise(int i, int i, int i) {...} ... public virtual void Initialise(int i, int i, ..., int i) {...} ... public virtual void SomethingElse() {...} ... } 

Cela ne devrait pas avoir un coût de performance plus élevé à moins que vous ayez beaucoup de valeurs de propriété par défaut et que vous le frappiez beaucoup.

 public class BaseClass { public BaseClass(params int[] parameters) { } } public class ChildClass : BaseClass { public ChildClass(params int[] parameters) : base(parameters) { } } 

Personnellement, je pense que c’est une erreur sur la partie Microsofts, ils auraient dû permettre au programmeur de remplacer la visibilité des constructeurs, méthodes et propriétés dans les classes de base, et de faire en sorte que les constructeurs soient toujours hérités.

De cette façon, il suffit simplement de remplacer (avec une visibilité plus faible – c.-à-d. Privée) les constructeurs que nous ne souhaitons PAS au lieu d’avoir à append tous les constructeurs que nous voulons. Delphi le fait de cette façon, et ça me manque.

Par exemple, si vous souhaitez remplacer la classe System.IO.StreamWriter, vous devez append les 7 constructeurs à votre nouvelle classe et, si vous aimez les commentaires, vous devez les commenter avec le XML d’en-tête. Pour aggraver les choses, la vue des métadonnées ne contient pas les commentaires XML comme des commentaires XML appropriés. Nous devons donc les copier ligne par ligne et les copier et coller. Que pensait Microsoft ici?

J’ai en fait écrit un petit utilitaire dans lequel vous pouvez coller le code de métadonnées et le convertir en commentaires XML en utilisant une visibilité superposée.

Dois-je vraiment copier tous les constructeurs de Foo dans Bar et Bah ? Et puis, si je change une signature de constructeur dans Foo , dois-je la mettre à jour dans Bar and Bah ?

Oui, si vous utilisez des constructeurs pour créer des instances.

N’y a-t-il aucun moyen d’hériter des constructeurs?

Nan.

N’y a-t-il aucun moyen d’encourager la réutilisation du code?

Eh bien, je ne dirai pas si l’inheritance des constructeurs serait une bonne ou une mauvaise chose et si cela encouragerait la réutilisation du code, puisque nous ne les avons pas et que nous ne les obtiendrons pas. 🙂

Mais ici, en 2014, avec le code C # actuel, vous pouvez obtenir quelque chose qui ressemble beaucoup aux constructeurs hérités en utilisant à la place une méthode de create générique. Cela peut être un outil utile à avoir dans votre ceinture, mais vous ne l’atteindrez pas à la légère. Je suis arrivé à ce point récemment face à la nécessité de transmettre quelque chose au constructeur d’un type de base utilisé dans quelques centaines de classes dérivées (jusqu’à récemment, la base n’avait pas besoin d’arguments, et le constructeur par défaut était bien les classes n’ont pas déclaré de constructeurs du tout, et ont obtenu celui fourni automatiquement).

Cela ressemble à ceci:

 // In Foo: public T create(int i) where: where T : Foo, new() { T obj = new T(); // Do whatever you would do with `i` in `Foo(i)` here, for instance, // if you save it as a data member; `obj.dataMember = i;` return obj; } 

Cela signifie que vous pouvez appeler la fonction de create générique en utilisant un paramètre de type qui est un sous-type de Foo qui a un constructeur avec un argument zéro.

Ensuite, au lieu de faire Bar b new Bar(42) , vous feriez ceci:

 var b = Foo.create(42); // or Bar b = Foo.create(42); // or var b = Bar.create(42); // But you still need the  bit // or Bar b = Bar.create(42); 

Là, j’ai montré que la méthode de create était directement sur Foo , mais bien sûr, cela pourrait être dans une classe de fabrique quelconque, si les informations qu’il configure peuvent être définies par cette classe de fabrique.

Juste pour plus de clarté: le nom create n’est pas important, il pourrait s’agir de makeThingy ou de tout ce que vous voulez.

Exemple complet

 using System.IO; using System; class Program { static void Main() { Bar b1 = Foo.create(42); b1.ShowDataMember("b1"); Bar b2 = Bar.create(43); // Just to show `Foo.create` vs. `Bar.create` doesn't matter b2.ShowDataMember("b2"); } class Foo { public int DataMember { get; private set; } public static T create(int i) where T: Foo, new() { T obj = new T(); obj.DataMember = i; return obj; } } class Bar : Foo { public void ShowDataMember(ssortingng prefix) { Console.WriteLine(prefix + ".DataMember = " + this.DataMember); } } } 

Le problème n’est pas que Bar et Bah doivent copier 387 constructeurs, le problème est que Foo a 387 constructeurs. Foo fait clairement trop de choses – refactor rapide! En outre, à moins que vous ayez une très bonne raison d’avoir des valeurs définies dans le constructeur (ce qui, si vous fournissez un constructeur sans paramètre, vous ne le faites probablement pas), je vous recommande d’utiliser la propriété getting / setting.

Non, vous n’avez pas besoin de copier tous les constructeurs sur Bar et Bah. Bar et Bah peuvent avoir autant de constructeurs que vous voulez indépendamment du nombre que vous définissez sur Foo. Par exemple, vous pouvez choisir d’avoir un seul constructeur Bar qui construit Foo avec le 212ème constructeur de Foo.

Oui, tous les constructeurs que vous modifiez dans Foo et dont dépendent Bar ou Bah vous demanderont de modifier Bar et Bah en conséquence.

Non, il n’y a aucun moyen dans .NET d’hériter des constructeurs. Mais vous pouvez réutiliser le code en appelant le constructeur d’une classe de base dans le constructeur de la sous-classe ou en appelant une méthode virtuelle que vous définissez (comme Initialize ()).

Vous pourrez peut-être adapter une version de l’ idiome du constructeur virtuel C ++ . Pour autant que je sache, C # ne prend pas en charge les types de retour covariants. Je crois que c’est sur les listes de souhaits de nombreuses personnes.

Trop de constructeurs est un signe de conception brisée. Mieux vaut une classe avec peu de constructeurs et la possibilité de définir des propriétés. Si vous avez vraiment besoin de contrôler les propriétés, considérez une fabrique dans le même espace de noms et rendez les propriétés internes. Laissez l’usine décider de la manière d’instancier la classe et de définir ses propriétés. L’usine peut avoir des méthodes qui prennent autant de parameters que nécessaire pour configurer l’object correctement.