Est-il préférable d'initialiser les variables des membres de classe lors de la déclaration
private List<Thing> _things = new List<Thing>();
private int _arb = 99;
ou dans le constructeur par défaut?
private List<Thing> _things;
private int _arb;
public TheClass()
{
_things = new List<Thing>();
_arb = 99;
}
Est-ce simplement une question de style ou y a-t-il des compromis de performance, d'une manière ou d'une autre?
En termes de performances, il n'y a pas de réelle différence; les initialiseurs de champ sont implémentés comme une logique constructeur. La seule différence est que les initialiseurs de champ se produisent avant tout constructeur "de base"/"ce".
L'approche constructeur peut être utilisée avec des propriétés implémentées automatiquement (les initialiseurs de champ ne le peuvent pas) - c'est-à-dire.
[DefaultValue("")]
public string Foo {get;set;}
public Bar() { // ctor
Foo = "";
}
En dehors de cela, j'ai tendance à préférer la syntaxe d'initialisation de champ; Je trouve que cela garde les choses localisées - c.-à-d.
private readonly List<SomeClass> items = new List<SomeClass>();
public List<SomeClass> Items {get {return items;}}
Je n'ai pas besoin d'aller chercher de haut en bas pour trouver où il est assigné ...
L'exception évidente est celle où vous devez exécuter une logique complexe ou traiter des paramètres de constructeur - auquel cas l'initialisation basée sur le constructeur est la voie à suivre. De même, si vous avez plusieurs constructeurs, il serait préférable que les champs soient toujours définis de la même manière - vous pourriez donc avoir des ctors comme:
public Bar() : this("") {}
public Bar(string foo) {Foo = foo;}
edit: comme commentaire latéral, notez que dans ce qui précède, s'il y a d'autres champs (non représentés) avec des initialiseurs de champ, alors ils ne sont directement initialisés que dans les constructeurs qui appellent base(...)
- c'est-à-dire la fonction public Bar(string foo)
ctor. L'autre constructeur ne pas exécute les initialiseurs de champ, car il sait qu'ils sont effectués par le cteur this(...)
.
En fait, les initialiseurs de terrain comme vous le démontrez sont un raccourci pratique. Le compilateur copie en fait le code d'initialisation au début de chaque constructeur d'instance que vous définissez pour votre type.
Cela a deux implications: premièrement, tout code d'initialisation de champ est dupliqué dans chaque constructeur et, deuxièmement, tout code que vous incluez dans vos constructeurs pour initialiser des champs à des valeurs spécifiques réaffectera en fait les champs.
Donc, en termes de performances, et en ce qui concerne la taille du code compilé, vous feriez mieux de déplacer les initialiseurs de champ dans les constructeurs.
D'un autre côté, l'impact sur les performances et le `` gonflement '' du code seront généralement négligeables, et la syntaxe d'initialisation de champ a l'avantage important de réduire le risque d'oublier d'initialiser un champ dans l'un de vos constructeurs.
Une limitation majeure des initialiseurs de champ est qu'il n'y a aucun moyen de les encapsuler dans un bloc try-finally. Si une exception est levée dans un initialiseur de champ, toutes les ressources allouées dans les initialiseurs précédents seront abandonnées; il n'y a aucun moyen de l'empêcher. D'autres erreurs de construction peuvent être traitées, si maladroitement, en ayant un constructeur de base protégé accepter un IDisposable par référence, et en le pointant sur lui-même comme sa toute première opération. On peut alors éviter d'appeler le constructeur sauf par des méthodes d'usine qui en cas d'exception appellera Dispose sur l'objet partiellement créé. Cette protection permettra le nettoyage des IDisposables créés dans les initialiseurs de classe dérivée si le constructeur de la classe principale échoue après avoir "supprimé" une référence au nouvel objet. Malheureusement, il n'y a aucun moyen de fournir une telle protection si un initialiseur de champ échoue.
Utilisez des initialiseurs de champ ou créez une fonction Init (). Le problème avec ces choses dans votre constructeur est que si vous avez besoin d'ajouter un deuxième constructeur, vous vous retrouvez avec du code copier/coller (ou vous l'ignorez et vous retrouvez avec des variables non initialisées).
Je voudrais soit initialiser où déclaré. Ou demandez au constructeur d'appeler une fonction Init ().
Pour les variables d'instance, c'est en grande partie une question de style (je préfère utiliser un constructeur). Pour les variables statiques, il y a un avantage de performance à l'initialisation en ligne (pas toujours possible, bien sûr).
Sur le point ajouté à ce qui précède - Vous avez toujours un constructeur lorsque vous implémentez des classes qui ont une implémentation. Si vous n'en déclarez pas un, l'instructeur par défaut est déduit par le compilateur [public Foo () {}]; un constructeur qui ne prend aucun argument.
Souvent, j'aime proposer les deux approches. Autorisez les constructeurs pour ceux qui souhaitent les utiliser et autorisez les initialiseurs de champ pour les situations où vous souhaitez utiliser une implémentation simplifiée ou par défaut de votre classe/type. Cela ajoute de la flexibilité à votre code. Gardez à l'esprit que n'importe qui peut utiliser l'initialiseur de champ par défaut s'il le souhaite ... assurez-vous de le déclarer manuellement si vous proposez plusieurs constructeurs - public Foo () {}
C'est vraiment comme tu veux.
Je les initialise souvent en ligne, parce que je n'aime pas avoir un constructeur quand je n'en ai pas vraiment besoin (j'aime les petites classes!).