2010-06-17 3 views
0

J'ai créé une classe qui est un croisement entre une singleton (fifth version) et une fabrique (dépendance injectable). Appelez cela une "Mono-Factory"? Il fonctionne, et ressemble à ceci:Comment retarder l'initialisation statique dans une propriété

public static class Context 
{ 
    public static BaseLogger LogObject = null; 

    public static BaseLogger Log 
    { 
     get 
     { 
      return LogFactory.instance; 
     } 
    } 

    class LogFactory 
    { 
     static LogFactory() { } 
     internal static readonly BaseLogger instance = LogObject ?? new BaseLogger(null, null, null); 
    } 
} 

//USAGE EXAMPLE: 
//Optional initialization, done once when the application launches... 
Context.LogObject = new ConLogger(); 

//Example invocation used throughout the rest of code... 
Context.Log.Write("hello", LogSeverity.Information); 

L'idée est pour l'usine mono pourrait être étendu pour traiter plus d'un élément (par exemple plus d'un enregistreur). Mais j'aurais aimé avoir fait l'aspect mono-usine comme ceci:

public static class Context 
{ 
    private static BaseLogger LogObject = null; 

    public static BaseLogger Log 
    { 
     get 
     { 
      return LogFactory.instance; 
     } 
     set 
     { 
      LogObject = value; 
     } 
    } 

    class LogFactory 
    { 
     static LogFactory() { } 
     internal static readonly BaseLogger instance = LogObject ?? new BaseLogger(null, null, null); 
    } 
} 

Le ne fonctionne pas, parce que le moment la propriété Log est touché (par un appel setter) provoque le chemin de code lié à le getter doit être exécuté ... ce qui signifie que les données "instance" internes de LogFactory sont toujours définies sur le BaseLogger (le réglage de "LogObject" est toujours trop tard!).

Y a-t-il une décoration ou une autre astuce que j'utiliserais pour que le chemin "get" de la propriété Log soit paresseux pendant que le chemin set est invoqué?

Répondre

3

Note: Ce est une réécriture complète de la réponse originale; la recommandation est toujours d'actualité.

Premièrement: assurez-vous de ne pas utiliser un débogueur. Par exemple, une fenêtre de surveillance pourrait toucher vos propriétés statiques publiques. C'est l'une des raisons possibles pour lesquelles le second exemple pourrait se comporter différemment du premier. Cela peut sembler idiot, mais on ne sait jamais.

Sous .NET 4, votre deuxième exemple fonctionne, et je voudrais honnêtement attendre à travailler sous .NET 2 aussi. Tant que vous ne touchez pas la propriété Context.Log ou le champ LogFactory.instance par inadvertance. Pourtant, il semble terriblement fragile.

En outre, strictement parlant, les beforefieldinit subtilités que vous essayez d'utiliser ici peut vous mordre dans une application multi-thread: l'initialisation de LogFactory n'a pas besoin de fonctionner sur le même fil que le poseur de Context.Log[Object]. Cela signifie que lorsque LogFactory.instance est initialisé, sur , le thread Context.LogObject n'a pas besoin d'être défini alors qu'il est sur un autre (de telles synchronisations peuvent se produire paresseusement). Donc c'est pas thread sûr. Vous pouvez essayer de résoudre ce problème en rendant Context.LogObjectvolatil, de cette façon l'ensemble est vu sur tous les threads à la fois. Mais qui sait quelles sont les autres conditions de course dans lesquelles nous nous retrouvons.

Et après toutes les astuces, vous êtes toujours à gauche avec le résultat suivant plutôt unintuitive:

Context.Log = value1; // OK 
Context.Log = value2; // IGNORED 

Vous vous attendez la deuxième invocation du compositeur soit le travail (Context.Log == value2) ou de jeter. Ne pas être ignoré silencieusement.

Vous pouvez également opter pour

public static class Context 
{ 
    private static BaseLogger LogObject; 

    public static BaseLogger Log 
    { 
     get { return LogObject ?? LogFactory.instance; } 
     set { LogObject = value; } 
    } 

    private class LogFactory 
    { 
     static LogFactory() {} 
     internal static readonly BaseLogger instance 
       = new BaseLogger(null, null, null); 
    } 
} 

Voici le résultat est garanti, et paresseux (conformément à la cinquième méthode singleton de Jon Skeet). Et il semble beaucoup plus propre à mon humble avis.

+0

@Ruben: J'ai supprimé ma réponse parce que je pense que c'était trompeur et/ou tout simplement faux. +1 pour votre suggestion finale, ce qui rend beaucoup plus évident ce que fait le code. – LukeH

+0

@Ruben: Si le CLR peut choisir arbitrairement la séquence d'initialisation, comme vous le suggérez, cela signifie-t-il que la cinquième version de Jon Skeet est rompue (http://www.yoda.arachsys.com/csharp/singleton.html) ? Quoi qu'il en soit, je vais probablement modifier mon code, peut-être pour lancer une exception après le premier "set", ou je vais utiliser ce que vous avez proposé. –

+0

Non, je pense que Jon Skeet a raison. Mais notez qu'il n'y a pas de dépendance dans son code pour tout ordre, * et * la classe Nested dans son exemple n'utilise aucune statique de la classe parente. Je pense que j'ai besoin d'ajuster quelque peu ma réponse, car je n'ai pas pris en compte certains des inconvénients de 'beforefieldinit'. – Ruben

0

Quelques conseils:

Découvrez Generics

-je éviter l'initialisation à l'aide statique. Cela peut causer des problèmes étranges dans la pratique. Par exemple, si ce que vous construisez génère une erreur, le chargeur de fenêtres vous dira qu'il y a un problème mais ne vous dira pas quoi. Votre code n'est jamais appelé, vous n'avez donc aucune chance de gérer le problème. Je construis la première instance quand elle est utilisée la première fois. Voici un exemple:

private static OrderCompletion instance; 

    /// <summary> 
    /// Get the single instance of the object 
    /// </summary> 
    public static OrderCompletion Instance 
    { 
     get 
     { 
      lock (typeof(OrderCompletion)) 
      { 
       if (instance == null) 
        instance = new OrderCompletion(); 
      } 
      return instance; 
     } 
    } 
+3

Cela semble assez inutile de verrouiller tous les accès, à tout le moins, vous pouvez mettre une deuxième copie de l'if (instance == null) en dehors de l'instruction lock pour éviter de verrouiller chaque invocation. –

+0

Je pense que cela va créer une fenêtre d'opportunité pour échouer dans un environnement threadé. – Jay

Questions connexes