C# Language
Constructeurs et finaliseurs
Recherche…
Introduction
Les constructeurs sont des méthodes d'une classe appelées lorsqu'une instance de cette classe est créée. Leur principale responsabilité est de laisser le nouvel objet dans un état utile et cohérent.
Les destructeurs / finaliseurs sont des méthodes d'une classe appelées lorsqu'une instance de celle-ci est détruite. En C #, ils sont rarement explicitement écrits / utilisés.
Remarques
C # n'a pas réellement de destructeurs, mais plutôt de finaliseurs qui utilisent la syntaxe de destructeur de style C ++. La spécification d'un destructeur remplace la méthode Object.Finalize()
qui ne peut pas être appelée directement.
Contrairement à d'autres langages ayant une syntaxe similaire, ces méthodes ne sont pas appelées lorsque des objets sont hors de portée, mais sont appelées lorsque le ramasse-miettes s'exécute, ce qui se produit sous certaines conditions . En tant que tels, ils ne sont pas garantis pour fonctionner dans un ordre particulier.
Les finaliseurs doivent être responsables du nettoyage des ressources non gérées uniquement (pointeurs acquis via la classe Marshal, reçus via p / Invoke (appels système) ou pointeurs bruts utilisés dans des blocs non sécurisés). Pour nettoyer les ressources gérées, consultez IDisposable, le modèle Dispose et l'instruction using
.
(Autres lectures: Quand devrais-je créer un destructeur? )
Constructeur par défaut
Lorsqu'un type est défini sans constructeur:
public class Animal
{
}
alors le compilateur génère un constructeur par défaut équivalent à ce qui suit:
public class Animal
{
public Animal() {}
}
La définition de tout constructeur pour le type supprimera la génération de constructeur par défaut. Si le type a été défini comme suit:
public class Animal
{
public Animal(string name) {}
}
alors un Animal
ne peut être créé qu'en appelant le constructeur déclaré.
// This is valid
var myAnimal = new Animal("Fluffy");
// This fails to compile
var unnamedAnimal = new Animal();
Pour le deuxième exemple, le compilateur affichera un message d'erreur:
'Animal' ne contient pas de constructeur qui prend 0 arguments
Si vous voulez qu'une classe possède à la fois un constructeur sans paramètre et un constructeur qui prend un paramètre, vous pouvez le faire en implémentant explicitement les deux constructeurs.
public class Animal
{
public Animal() {} //Equivalent to a default constructor.
public Animal(string name) {}
}
Le compilateur ne sera pas en mesure de générer un constructeur par défaut si la classe étend une autre classe qui ne possède pas de constructeur sans paramètre. Par exemple, si nous avions une classe Creature
:
public class Creature
{
public Creature(Genus genus) {}
}
alors Animal
défini comme class Animal : Creature {}
ne compile pas.
Appeler un constructeur d'un autre constructeur
public class Animal
{
public string Name { get; set; }
public Animal() : this("Dog")
{
}
public Animal(string name)
{
Name = name;
}
}
var dog = new Animal(); // dog.Name will be set to "Dog" by default.
var cat = new Animal("Cat"); // cat.Name is "Cat", the empty constructor is not called.
Constructeur statique
Un constructeur statique est appelé la première fois qu'un membre d'un type est initialisé, un membre de classe statique est appelé ou une méthode statique. Le constructeur statique est thread-safe. Un constructeur statique est couramment utilisé pour:
- Initialiser l'état statique, c'est-à-dire l'état qui est partagé entre différentes instances de la même classe.
- Créer un singleton
Exemple:
class Animal
{
// * A static constructor is executed only once,
// when a class is first accessed.
// * A static constructor cannot have any access modifiers
// * A static constructor cannot have any parameters
static Animal()
{
Console.WriteLine("Animal initialized");
}
// Instance constructor, this is executed every time the class is created
public Animal()
{
Console.WriteLine("Animal created");
}
public static void Yawn()
{
Console.WriteLine("Yawn!");
}
}
var turtle = new Animal();
var giraffe = new Animal();
Sortie:
Animal initialisé
Animal créé
Animal créé
Si le premier appel est à une méthode statique, le constructeur statique est appelé sans le constructeur d'instance. Ceci est correct, car la méthode statique ne peut pas accéder à l'état de l'instance de toute façon.
Animal.Yawn();
Cela va sortir:
Animal initialisé
Bâillement!
Voir aussi Exceptions dans les constructeurs statiques et les constructeurs statiques génériques .
Exemple singleton:
public class SessionManager
{
public static SessionManager Instance;
static SessionManager()
{
Instance = new SessionManager();
}
}
Appel du constructeur de la classe de base
Un constructeur d'une classe de base est appelé avant qu'un constructeur d'une classe dérivée ne soit exécuté. Par exemple, si Mammal
étend Animal
, le code contenu dans le constructeur de Animal
est appelé en premier lors de la création d'une instance de Mammal
.
Si une classe dérivée ne spécifie pas explicitement quel constructeur de la classe de base doit être appelé, le compilateur assume le constructeur sans paramètre.
public class Animal
{
public Animal() { Console.WriteLine("An unknown animal gets born."); }
public Animal(string name) { Console.WriteLine(name + " gets born"); }
}
public class Mammal : Animal
{
public Mammal(string name)
{
Console.WriteLine(name + " is a mammal.");
}
}
Dans ce cas, l’instanciation d’un Mammal
en appelant un new Mammal("George the Cat")
imprimera
Un animal inconnu naît.
George le chat est un mammifère.
L'appel d'un constructeur différent de la classe de base se fait en plaçant : base(args)
entre la signature du constructeur et son corps:
public class Mammal : Animal
{
public Mammal(string name) : base(name)
{
Console.WriteLine(name + " is a mammal.");
}
}
Appeler un new Mammal("George the Cat")
va maintenant imprimer:
George le chat naît.
George le chat est un mammifère.
Finalisateurs sur les classes dérivées
Lorsqu'un graphe d'objet est finalisé, l'ordre est l'inverse de la construction. Par exemple, le super-type est finalisé avant le type de base, comme le montre le code suivant:
class TheBaseClass
{
~TheBaseClass()
{
Console.WriteLine("Base class finalized!");
}
}
class TheDerivedClass : TheBaseClass
{
~TheDerivedClass()
{
Console.WriteLine("Derived class finalized!");
}
}
//Don't assign to a variable
//to make the object unreachable
new TheDerivedClass();
//Just to make the example work;
//this is otherwise NOT recommended!
GC.Collect();
//Derived class finalized!
//Base class finalized!
Modèle de constructeur singleton
public class SingletonClass
{
public static SingletonClass Instance { get; } = new SingletonClass();
private SingletonClass()
{
// Put custom constructor code here
}
}
Le constructeur étant privé, aucune nouvelle instance de SingletonClass
ne peut être créée en consommant du code. Le seul moyen d'accéder à l'instance unique de SingletonClass
consiste à utiliser la propriété statique SingletonClass.Instance
.
La propriété Instance
est affectée par un constructeur statique généré par le compilateur C #. Le runtime .NET garantit que le constructeur statique est exécuté au plus une fois et est exécuté avant la première lecture de l' Instance
. Par conséquent, toutes les préoccupations relatives à la synchronisation et à l'initialisation sont exécutées par le moteur d'exécution.
Notez que si le constructeur statique échoue, la classe Singleton
devient définitivement inutilisable pour la vie de AppDomain.
De même, l'exécution du constructeur statique n'est pas garantie au moment du premier accès à Instance
. Au contraire, il fonctionnera à un moment donné avant cela . Cela rend le temps auquel l'initialisation se produit non déterministe. Dans des cas pratiques, JIT appelle souvent le constructeur statique lors de la compilation (et non de l'exécution) d'une Instance
référençant une méthode. Ceci est une optimisation des performances.
Reportez-vous à la page Implémentations Singleton pour savoir comment implémenter le modèle singleton.
Forcer un constructeur statique à être appelé
Alors que les constructeurs statiques sont toujours appelés avant la première utilisation d'un type, il est parfois utile de pouvoir les forcer à être appelés et la classe RuntimeHelpers
fournit une aide:
using System.Runtime.CompilerServices;
// ...
RuntimeHelpers.RunClassConstructor(typeof(Foo).TypeHandle);
Remarque : Toutes les initialisations statiques (les initialiseurs de champs par exemple) seront exécutées, pas seulement le constructeur lui-même.
Utilisations potentielles : forcer l'initialisation pendant l'écran de démarrage dans une application d'interface utilisateur ou s'assurer qu'un constructeur statique n'échoue pas dans un test unitaire.
Appeler des méthodes virtuelles dans un constructeur
Contrairement à C ++ en C #, vous pouvez appeler une méthode virtuelle à partir du constructeur de classe (OK, vous pouvez aussi en C ++, mais le comportement est surprenant au premier abord). Par exemple:
abstract class Base
{
protected Base()
{
_obj = CreateAnother();
}
protected virtual AnotherBase CreateAnother()
{
return new AnotherBase();
}
private readonly AnotherBase _obj;
}
sealed class Derived : Base
{
public Derived() { }
protected override AnotherBase CreateAnother()
{
return new AnotherDerived();
}
}
var test = new Derived();
// test._obj is AnotherDerived
Si vous venez d'un arrière-plan C ++, c'est surprenant, le constructeur de la classe de base voit déjà la table des méthodes virtuelles de la classe dérivée!
Attention : la classe dérivée n'est peut-être pas encore complètement initialisée (son constructeur sera exécuté après le constructeur de la classe de base) et cette technique est dangereuse (il y a aussi un avertissement StyleCop pour cela). Habituellement, cela est considéré comme une mauvaise pratique.
Constructeurs statiques génériques
Si le type sur lequel le constructeur statique est déclaré est générique, le constructeur statique sera appelé une fois pour chaque combinaison unique d'arguments génériques.
class Animal<T>
{
static Animal()
{
Console.WriteLine(typeof(T).FullName);
}
public static void Yawn() { }
}
Animal<Object>.Yawn();
Animal<String>.Yawn();
Cela va sortir:
System.Object
System.String
Voir aussi Comment fonctionnent les constructeurs statiques pour les types génériques?
Exceptions dans les constructeurs statiques
Si un constructeur statique lève une exception, il ne sera jamais réessayé. Le type est inutilisable pour la durée de vie de AppDomain. Toute autre utilisation du type TypeInitializationException
une TypeInitializationException
autour de l'exception d'origine.
public class Animal
{
static Animal()
{
Console.WriteLine("Static ctor");
throw new Exception();
}
public static void Yawn() {}
}
try
{
Animal.Yawn();
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
try
{
Animal.Yawn();
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
Cela va sortir:
Stator statique
System.TypeInitializationException: L'initialiseur de type pour 'Animal' a généré une exception. ---> System.Exception: Une exception de type 'System.Exception' a été levée.
[...]
System.TypeInitializationException: L'initialiseur de type pour 'Animal' a généré une exception. ---> System.Exception: Une exception de type 'System.Exception' a été levée.
où vous pouvez voir que le constructeur actuel n'est exécuté qu'une seule fois et que l'exception est réutilisée.
Initialisation du constructeur et de la propriété
L'affectation de la valeur de la propriété doit-elle être exécutée avant ou après le constructeur de la classe?
public class TestClass
{
public int TestProperty { get; set; } = 2;
public TestClass()
{
if (TestProperty == 1)
{
Console.WriteLine("Shall this be executed?");
}
if (TestProperty == 2)
{
Console.WriteLine("Or shall this be executed");
}
}
}
var testInstance = new TestClass() { TestProperty = 1 };
Dans l'exemple ci-dessus, la valeur TestProperty
doit- TestProperty
être 1
dans le constructeur de la classe ou après le constructeur de classe?
Affectez des valeurs de propriété dans la création d'instance comme ceci:
var testInstance = new TestClass() {TestProperty = 1};
Sera exécuté après l'exécution du constructeur. Toutefois, l'initialisation de la propriété dans la propriété de la classe dans C # 6.0 ressemble à ceci:
public class TestClass
{
public int TestProperty { get; set; } = 2;
public TestClass()
{
}
}
sera fait avant que le constructeur ne soit exécuté.
Combinant les deux concepts ci-dessus dans un seul exemple:
public class TestClass
{
public int TestProperty { get; set; } = 2;
public TestClass()
{
if (TestProperty == 1)
{
Console.WriteLine("Shall this be executed?");
}
if (TestProperty == 2)
{
Console.WriteLine("Or shall this be executed");
}
}
}
static void Main(string[] args)
{
var testInstance = new TestClass() { TestProperty = 1 };
Console.WriteLine(testInstance.TestProperty); //resulting in 1
}
Résultat final:
"Or shall this be executed"
"1"
Explication:
La valeur TestProperty
sera d'abord attribuée en tant que 2
, puis le constructeur TestClass
sera exécuté, entraînant l'impression de
"Or shall this be executed"
Et puis, la TestProperty
sera affectée à 1
raison de la new TestClass() { TestProperty = 1 }
, ce qui rend la valeur finale de TestProperty
imprimée par Console.WriteLine(testInstance.TestProperty)
"1"