2010-01-28 5 views
4

Consultez le code suivant:Existe-t-il un moyen de forcer l'initialisation des champs statiques en C#?

class Program 
{ 
    static Program() { 
     Program.program1.Value = 5; 
    } 

    static List<Program> values = new List<Program>(); 
    int value; 
    int Value 
    { 
     get { return value; } 
     set { 
      this.value = value; 
      Program.values.Add(this); 
     } 
    } 

    static Program program1 = new Program { value = 1 }; 
    static Program program2 = new Program { value = 2 }; 
    static Program program3 = new Program { value = 3 }; 

    static void Main(string[] args) 
    { 
     if (Program.values.Count == 0) Console.WriteLine("Empty"); 
     foreach (var value in Program.values) 
      Console.WriteLine(value.Value); 
     Console.ReadKey(); 
    } 
} 

Il imprime seulement le numéro 5, et si supprimé le code dans le constructeur statique, il imprime « vide ».

Existe-t-il un moyen de forcer l'initialisation des champs statiques même s'ils ne sont pas encore utilisés?

J'ai besoin d'une propriété statique nommée Valeurs avec toutes les occurrences du type référencé.

J'ai essayé quelques variantes de ce code et certaines fonctionnent pour certains types mais pas pour d'autres.

EDIT: L'EXEMPLE CI-DESSUS BROKEN, FAITES L'ESSAI UN:

class Subclass<T> { 
    static Subclass() 
    { 
     Values = new List<Subclass<T>>(); 
    } 
    public Subclass() 
    { 
     if (!Values.Any(i => i.Value.Equals(this.Value))) 
     { 
      Values.Add(this); 
     } 
    } 

    public T Value { get; set; } 

    public static List<Subclass<T>> Values { get; private set; } 
} 

class Superclass : Subclass<int> 
{ 
    public static Superclass SuperclassA1 = new Superclass { Value = 1 }; 
    public static Superclass SuperclassA2 = new Superclass { Value = 2 }; 
    public static Superclass SuperclassA3 = new Superclass { Value = 3 }; 
    public static Superclass SuperclassA4 = new Superclass { Value = 4 }; 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     //Console.WriteLine(Superclass.SuperclassA1); //UNCOMMENT THIS LINE AND IT WORKS 
     foreach (var value in Superclass.Values) 
     { 
      Console.WriteLine(value.Value); 
     } 
     Console.ReadKey(); 
    } 
} 

Répondre

5

La réponse à votre question est 'bien, oui'. Mais l'une des deux façons de "forcer" c'est ce que vous faites déjà.

La section pertinente dans la spécification du langage est 10.11 Static constructors, et plus précisément:

« Le constructeur statique pour une classe exécute au plus une fois dans un domaine d'application donné L'exécution d'un constructeur statique est déclenchée par la première. les événements suivants se produisent dans un domaine d'application:..

  • une instance de la classe est créée
  • Tous les membres statiques de la classe sont référencés

Si une classe contient la méthode Main (Section 3.1) dans laquelle l'exécution commence, le constructeur statique de cette classe s'exécute avant que la méthode Main ne soit appelée. Si une classe contient des champs statiques avec initializers, les initialiseurs sont exécutées dans l'ordre textuel immédiatement avant l'exécution du constructeur statique. »

+0

Ouais! J'ai lu ceci dans la spécification aussi. Mais j'espérais qu'il y avait un autre moyen, pas officiel, de le faire. Merci! –

+0

S'il y a un moyen non officiel, j'aimerais en entendre parler! Mais peut-être devrait-on éviter de telles techniques. 'Unofficial.Equals (Hack)'? –

+0

@ChristofferLette Pour le cas de Rafael, il existe en fait une autre manière qui apparaît dans MSDN et qui revient à .Net 1.1, 'System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor (..)'. Voir ma réponse pour une variation de travail de l'exemple de Rafael. –

2

ressemble en fait vous 'valeur' ​​mal orthographié -> 'Valeur' ​​ Ainsi:

static Program program1 = new Program { Value = 1 }; 
    static Program program2 = new Program { Value = 2 }; 
    static Program program3 = new Program { Value = 3 }; 

jolies imprime plus de lignes

4

Mais vous ne définissez jamais la propriété - au lieu de cela, vous définissez le champ de sauvegarde directement, ne passant pas par votre logique pour l'ajouter à la liste statique lors de la création de programme1, p rogram2 et program3.

dire que vous devez changer:

static Program program1 = new Program { value = 1 }; 
    static Program program2 = new Program { value = 2 }; 
    static Program program3 = new Program { value = 3 }; 

à:

static Program program1 = new Program { Value = 1 }; 
    static Program program2 = new Program { Value = 2 }; 
    static Program program3 = new Program { Value = 3 }; 
+1

C'est la raison pour laquelle nous ne pas utiliser le même nom de variable pour un membre et un nom de propriété! –

+0

Bien sûr, votre code vous permet d'ajouter plusieurs fois la même instance à votre liste. Vous pouvez donc déplacer la logique "add to static list" vers le constructeur ou simplement vérifier si elle figure déjà dans la liste. –

+0

Okay! Mon échantillon est cassé. J'ai mal saisi la propriété Value par le champ de valeur. Mais malheureusement ce n'est pas ce qui se passe dans mon code actuel. Je vais remplacer l'exemple par un autre qui ressemble plus à mon code actuel, en utilisant le constructeur pour ajouter les instances à la liste des valeurs. Je pense que cela a quelque chose à voir avec ce que John Skeet décrit ici: http://msmvps.com/blogs/jon_skeet/archive/2010/01/26/type-initialization-changes-in-net-4-0. aspx Merci quand même! –

0

Le second échantillon ne fonctionne pas simplement parce que Value est membre statique de Subclass.

La syntaxe C# permet Superclass.Values, mais éventuellement l'appel de la méthode compilée sera au getter Subclass.Values, donc le type Superclass n'est jamais réellement touché, Superclass.SuperclassA1 d'autre part touche le type et déclenche l'initialisation statique.C'est pourquoi C# n'a pas vraiment d'initialisation statique implicite et vous avez besoin de frameworks de composition comme MEF et Unity.

3

Il existe en fait un moyen de forcer l'initialisation des propriétés dans ce cas. La modification nécessite l'ajout d'un paramètre de type à la classe de base pour représenter la future sous-classe de la classe de base qui contiendra les champs à initialiser. Ensuite, nous pouvons utiliser RuntimeHelpers.RunClassConstructor pour nous assurer que les champs statiques de sous-classe sont initialisés.

Ce qui suit donne les résultats que vous recherchez:

class Subclass<TSubclass, T> 
{ 
    static Subclass() 
    { 
     Values = new List<Subclass<TSubclass, T>>(); 
     // This line is where the magic happens 
     System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(TSubclass).TypeHandle); 
    } 
    public Subclass() 
    { 
     if (!Values.Any(i => i.Value.Equals(this.Value))) 
     { 
      Values.Add(this); 
     } 
    } 

    public T Value { get; set; } 

    public static List<Subclass<TSubclass, T>> Values { get; private set; } 
} 

class Superclass : Subclass<Superclass, int> 
{ 
    public static Superclass SuperclassA1 = new Superclass { Value = 1 }; 
    public static Superclass SuperclassA2 = new Superclass { Value = 2 }; 
    public static Superclass SuperclassA3 = new Superclass { Value = 3 }; 
    public static Superclass SuperclassA4 = new Superclass { Value = 4 }; 
} 

public class Program 
{ 
    public static void Main() 
    { 
     foreach (var value in Superclass.Values) 
     { 
      Console.WriteLine(value.Value); 
     } 
     Console.ReadKey(); 
    } 
} 

Ce qui se passe est, l'appel à RuntimeHelpers.RunClassConstructor(typeof(TSubclass).TypeHandle) force le constructeur statique de TSubclass pour exécuter si elle n'a pas déjà exécuté. Cela garantit que les champs statiques ont initialisé d'abord comme par cette ligne de https://msdn.microsoft.com/en-us/library/aa645612(VS.71).aspx:

Si une classe contient des champs statiques avec initializers, les initialiseurs sont exécutées dans l'ordre textuel immédiatement avant l'exécution du constructeur statique.

Voici une démonstration dotnetfiddle fonctionner:

https://dotnetfiddle.net/MfXzFd

+0

Vous avez sauvé ma journée monsieur. +1 de moi. –

+0

@ S.TarıkÇetin Heureux d'entendre que cela vous a aidé! –

+0

C'est incroyable, c'est assez proche de ce dont j'ai besoin! La seule chose qui le ferait serait si Superclass peut être dérivé aussi et le nouveau champ sera également initialisé. Comme ceci: https://dotnetfiddle.net/RGBghM La seule façon que je sais est de faire 'Superclass' que je ne veux pas, parce que c'est mon implémentation par défaut et' SuperClassCustom' sont des personnalisations spécifiques pour un client spécifique . – Wolfsblvt