2010-03-31 2 views
2

Je pensais à un bon objet d'exception générique qui remplacerait throw new Exception(string.Format("...",...)), à la fois pour simplifier et aussi pour accélérer de tels objets. Le formatage lent String.Format() doit être différé jusqu'à ce que la propriété Message soit appelée. La sérialisation est également quelque peu risquée. En outre, un tel objet pourrait ultérieurement implémenter la localisation.FormattedException au lieu de lancer new Exception (string.Format (...)) dans .NET

Mise à jour: Cette exception doit être héritée par des exceptions utilisateur plus spécifiques, et non par elle-même. Désolé de ne pas être clair.

Voici ce que j'ai trouvé. S'il vous plaît commenter s'il y a des moyens de l'améliorer. Merci!

/// <summary> 
/// Generic exception capable of delayed message formatting. 
/// Inherit for more specific exceptions. 
/// </summary> 
[Serializable] 
public class FormattedException : Exception 
{ 
    private readonly object[] _arguments; 
    private readonly string _formatStr; 
    private readonly bool _useFormat; 

    private FormattedException(bool useFormat, Exception inner, string message, params object[] args) 
     : base(message, inner) 
    { 
     _useFormat = useFormat; 
     _formatStr = message; 
     _arguments = args; 
    } 

    public FormattedException() 
     : this(false, null, null, null) 
    {} 

    public FormattedException(string message) 
     : this(false, null, message, null) 
    {} 

    public FormattedException(string message, params object[] args) 
     : this(true, null, message, args) 
    {} 

    public FormattedException(Exception inner, string message) 
     : this(false, inner, message, null) 
    {} 

    public FormattedException(Exception inner, string message, params object[] args) 
     : this(true, inner, message, args) 
    {} 

    public override string Message 
    { 
     get 
     { 
      if (!_useFormat) 
       return _formatStr; 

      try 
      { 
       return string.Format(_formatStr, _arguments); 
      } 
      catch (Exception ex) 
      { 
       var sb = new StringBuilder(); 

       sb.Append("Error formatting exception: "); 
       sb.Append(ex.Message); 
       sb.Append("\nFormat string: "); 
       sb.Append(_formatStr); 
       if (_arguments != null && _arguments.Length > 0) 
       { 
        sb.Append("\nArguments: "); 
        for (int i = 0; i < _arguments.Length; i++) 
        { 
         if (i > 0) sb.Append(", "); 
         try 
         { 
          sb.Append(_arguments[i]); 
         } 
         catch (Exception ex2) 
         { 
          sb.AppendFormat("(Argument #{0} cannot be shown: {1})", i, ex2.Message); 
         } 
        } 
       } 

       return sb.ToString(); 
      } 
     } 
    } 

    #region Serialization 

    private const string SerializationField = "FormatString"; 

    protected FormattedException(SerializationInfo info, StreamingContext context) 
     : base(info, context) 
    { 
     _formatStr = (string) info.GetValue(SerializationField, typeof (string)); 
     // Leave other values at their default 
    } 

    public override void GetObjectData(SerializationInfo info, StreamingContext context) 
    { 
     base.GetObjectData(info, context); 
     // To avoid any serialization issues with param objects, format message now 
     info.AddValue(SerializationField, Message, typeof (string)); 
    } 

    #endregion 
} 
+2

Vous êtes préoccupé par la performance de string.Format() en lançant une exception? À quelle fréquence prévoyez-vous des exceptions? – Henrik

+0

Le vrai "problème", il semble que vous essayez de résoudre est que String.Format représente 13 caractères de fouillis visuel ennuyeux. C'est le cas, mais si vous trouvez cela gênant, vous devez définir une méthode statique privée qui s'y accole. Je ne suggérerais pas de différer le format jusqu'à ce que l'exception soit vue, car il n'y a aucune garantie que tous les objets mutables auxquels l'exception détient une référence n'auront pas changé d'ici là. – supercat

Répondre

0

Vous ne devez pas sérialiser les arguments également?

+0

Non, car les objets transmis en tant qu'arguments peuvent ne pas être sérialisables ou ne pas exister de l'autre côté (accès distant). Il est beaucoup plus sûr de sérialiser la chaîne que de traiter les problèmes de désérialisation. – Yurik

3

C'est une idée intéressante, mais pas une bonne idée. La raison de créer une exception personnalisée n'a rien à voir avec la facilité d'utilisation - créez uniquement une exception personnalisée si quelqu'un doit attraper ce type d'exception et faire quelque chose de différent avec celui-ci. Au lieu d'une exception personnalisée, vous pouvez peut-être créer une méthode d'extension.

+0

Ceci est juste une classe de base pour les autres exceptions de l'utilisateur! J'envisageais même de le rendre abstrait, mais j'ai décidé de le rejeter parce que System.Exception est aussi une classe publique. Les méthodes d'extension ont un certain nombre de limitations: string.Format() va se produire tout de suite, causant un retard inutile, pas facile de lancer plusieurs types. Si vous regardez comment ArgumentOutOfRangeException est effectué, il retarde la mise en forme jusqu'à ce que la propriété Message soit appelée. – Yurik

0

Les stratégies de gestion des exceptions sont basées sur les types d'exception et non sur les messages d'exception. Si vous voulez que l'utilisateur gère votre exception, vous devez lui ajouter une sémantique significative. Mais je ne comprends vraiment pas comment je peux gérer votre exception (expept pour le montrer à l'utilisateur).

Et je pense que ce n'est pas une bonne idée de calculer le message à l'intérieur de la propriété Message (l'accès multiple à celui-ci peut causer un inconvénient de performance significatif). Et tous vos biens devraient se comporter comme des champs (here j'en ai parlé un peu).

+0

D'accord - cela a été conçu comme une classe de base pour des exceptions plus spécifiques, ne pas être lancé par lui-même. En ce qui concerne l'informatique, la propriété de message sera appelée beaucoup moins fréquemment que le lancement/attrapage d'exception, car seuls les systèmes de journalisation/d'affichage des utilisateurs l'examineront. Je ne voulais pas faire face aux problèmes de multithreading de la mise en cache du résultat. – Yurik

+0

Hmmm. Et qu'est-ce qui peut empêcher vos classes dérivées de surcharger votre propriété Message? Je pense que dans ce cas, vous devriez utiliser l'agrégation, pas l'héritage. –

2

Ceci, IMO, n'est pas un très bon design. Il suffit de regarder votre premier paramètre pour l'exception FormattedException (useFormat) qui sent mauvais. Si ce n'est pas une exception formatée (useFormat = false), pourquoi est-ce que j'utilise une exception FormattedException? Cela montre un mauvais design. Ce qui mène essentiellement à ceci: vous abusez de l'héritage.

Vous utilisez l'héritage sous la forme d'une classe UTILS, ou un moyen d'avoir beaucoup de fonctionnalités communes et faciles dans de nombreuses classes. Ceci est censé vous aider à avoir une approche DRY, sauf que je pense que ce n'est pas une approche très OO. Cette relation représente-t-elle la relation "IS A"? Je pense que non. Je pense que cela représente une relation "A", ce qui signifie que la classe a la possibilité de créer une exception formatée, au lieu d'une exception formatée "Est-ce une".

Peut-être qu'une interface est plus appropriée? Peut-être un motif de décorateur? Peut-être quelque chose d'autre? Avez-vous pensé à d'autres solutions que l'héritage? IMO ce dont vous avez besoin est une Usine simple (peut-être en utilisant des méthodes d'Extension) ... qui retourne n'importe quel type d'exception que vous lui donnez.

Bien sûr, ce n'est que mes .02 cents, donc je pourrais être complètement éteint.

Oh, BTW, ceux qui essayent/attrapent à l'intérieur de l'Exception font mes yeux saigner.

+0

Pourrions être plus d'accord. – MatteoSp

1

J'ai une classe similaire que j'utilise depuis plusieurs années, également appelée FormattedException.Une chose que vous devriez faire est de rendre la classe abstraite de sorte qu'elle doive être héritée par les classes d'exception qui utilisent les surcharges de constructeur de formatage étendues. Je ne m'inquiéterais pas des implications en termes de performances de l'utilisation de String.Format() en interne car une fois qu'une exception est levée, un impact beaucoup plus important sur les performances globales de l'application aura lieu et l'impact de String.Format() sur les performances est insignifiant . Je suis d'accord dans votre approche globale pour simplifier le code utilisé pour lancer des exceptions avec un message de chaîne formaté et je me suis souvent retrouvé plonger un appel à String.Format() pour le paramètre message de nombreuses exceptions que j'ai levées. J'ai jeté ensemble un projet et un paquet NuGet pour ma version de FormattedException que j'ai utilisé et publié sur GitHub et NuGet Gallery.

FormattedException source on GutHub

FormattedException NuGet package

Questions connexes