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éé

Voir la démo

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.

Voir la démo

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.

Voir la démo

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"


Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow