Quel est le meilleur moyen de s’assurer que le constructeur statique d’une classe de base est appelé?

La documentation sur les constructeurs statiques en C # indique:

Un constructeur statique est utilisé pour initialiser des données statiques ou pour effectuer une action particulière à exécuter une seule fois. Il est appelé automatiquement avant la création de la première instance ou la référence à des membres statiques .

Cette dernière partie (environ quand il est appelé automatiquement) m’a lancé pour une boucle; jusqu’à la lecture de cette partie, j’ai pensé qu’en accédant simplement à une classe de quelque manière que ce soit , je pouvais être sûr que le constructeur statique de sa classe de base avait été appelé. Les tests et l’examen de la documentation ont révélé que ce n’était pas le cas; il semble que l’exécution du constructeur statique d’une classe de base ne soit garantie que lorsqu’un membre de cette classe de base est spécifiquement utilisé.

Maintenant, je suppose que dans la plupart des cas, lorsque vous traitez avec une classe dérivée, vous construisez une instance et cela constituerait une instance de la classe de base en cours de création. Le constructeur statique serait appelé. Mais si je ne traite que des membres statiques de la classe dérivée , alors quoi?

Pour rendre cela un peu plus concret, j’ai pensé que le code ci-dessous fonctionnerait:

abstract class TypeBase { static TypeBase() { Type.Name = "int"; Type.Name = "long"; Type.Name = "double"; } } class Type : TypeBase { public static ssortingng Name { get; internal set; } } class Program { Console.WriteLine(Type.Name); } 

J’ai supposé TypeBase classe Type invoquerait automatiquement le constructeur statique pour TypeBase ; mais cela ne semble pas être le cas. Type.Name est null et le code ci-dessus renvoie la chaîne vide.

Mis à part la création d’un membre factice (comme une méthode statique Initialize() qui ne fait rien), existe-t-il un meilleur moyen de s’assurer que le constructeur statique d’un type de base sera appelé avant que ses types dérivés ne soient utilisés?

Si non, alors … membre factice c’est!

Les règles sont très complexes et, entre le CLR 2.0 et le 4.0, elles ont en fait changé de manière subtile et intéressante , et l’OMI rend les approches les plus “intelligentes” fragiles entre les versions du CLR. Une méthode Initialize() peut également ne pas exécuter le travail dans CLR 4.0 si elle ne touche pas les champs.

Je rechercherais une autre conception ou utiliserais éventuellement une initialisation lente dans votre type (c.-à-d. Vérifiez un bit ou une référence (par rapport à null ) pour voir si cela a été fait).

Vous pouvez appeler explicitement le constructeur statique afin de ne pas avoir à créer de méthode d’initialisation:

 System.Runtime.ComstackrServices.RuntimeHelpers.RunClassConstructor(typeof (TypeBase).TypeHandle); 

Vous pouvez l’appeler dans le constructeur statique de la classe dérivée.

Comme d’autres l’ont noté, votre parsing est correcte. La spécification est implémentée littéralement ici; comme aucun membre de la classe de base n’a été appelé et qu’aucune instance n’a été créée, le constructeur statique de la classe de base n’est pas appelé. Je peux voir à quel point cela peut être surprenant, mais il s’agit d’une implémentation ssortingcte et correcte de la spécification.

Je n’ai aucun conseil pour vous autre que “si ça vous fait mal, ne faites pas ça.” Je voulais juste souligner que le cas contraire peut aussi vous mordre:

 class Program { static void Main(ssortingng[] args) { DM(); } } class B { static B() { Console.WriteLine("B"); } public static void M() {} } class D: B { static D() { Console.WriteLine("D"); } } 

Ceci affiche “B” alors que “un membre de D” a été invoqué. M est membre de D uniquement par inheritance; le CLR n’a aucun moyen de distinguer si BM a été invoqué “par D” ou “par B”.

Lors de tous mes tests, je n’ai réussi à appeler qu’un membre factice de la base pour que celle-ci appelle son constructeur statique, comme illustré:

 class Base { static Base() { Console.WriteLine("Base static constructor called."); } internal static void Initialize() { } } class Derived : Base { static Derived() { Initialize(); //Removing this will cause the Base static constructor not to be executed. Console.WriteLine("Derived static constructor called."); } public static void DoStaticStuff() { Console.WriteLine("Doing static stuff."); } } class Program { static void Main(ssortingng[] args) { Derived.DoStaticStuff(); } } 

L’autre option consistait à inclure un membre statique en lecture seule dans le typé dérivé qui effectuait les opérations suivantes:

private static readonly Base myBase = new Base();

Cependant, cela ressemble à un hack (bien que le membre factice en fasse de même) juste pour que le constructeur statique de base soit appelé.

Je regrette presque toujours de compter sur quelque chose comme ça. Les méthodes statiques et les classes peuvent vous limiter plus tard. Si vous vouliez coder un comportement spécial pour votre classe Type plus tard, vous seriez encerclé.

Donc, voici une légère variation de votre approche. C’est un peu plus de code, mais cela vous permettra de définir ultérieurement un type personnalisé qui vous permettra de faire des choses personnalisées.

  abstract class TypeBase { private static bool _initialized; protected static void Initialize() { if (!_initialized) { Type.Instance = new Type {Name = "int"}; Type.Instance = new Type {Name = "long"}; Type.Instance = new Type {Name = "double"}; _initialized = true; } } } class Type : TypeBase { private static Type _instance; public static Type Instance { get { Initialize(); return _instance; } internal set { _instance = value; } } public ssortingng Name { get; internal set; } } 

Ensuite, lorsque vous appendez une méthode virtuelle à Type et voudrez une implémentation spéciale pour Type, vous pouvez le mettre en œuvre de la manière suivante:

 class TypeInt : Type { public override ssortingng Foo() { return "Int Fooooo"; } } 

Et puis connectez-le en changeant

 protected static void Initialize() { if (!_initialized) { Type.Instance = new TypeInt {Name = "int"}; Type.Instance = new Type {Name = "long"}; Type.Instance = new Type {Name = "double"}; _initialized = true; } } 

Mon conseil serait d’éviter les constructeurs statiques – c’est facile à faire. Evitez également les classes statiques et, si possible, les membres statiques. Je ne dis pas jamais, mais avec parcimonie. Préfère un singleton d’une classe à un statique.

Juste une idée, vous pouvez faire quelque chose comme ça:

  abstract class TypeBase { static TypeBase() { Type.Name = "int"; Type.Name = "long"; Type.Name = "double"; } } class Type : TypeBase { static Type() { new Type(); } public static ssortingng Name { get; internal set; } } class Program { Console.WriteLine(Type.Name); }