2010-04-20 6 views
2

Comment puis-je gérer toutes les exceptions pour une classe similaire au suivant dans certaines circonstances?Comment puis-je gérer toutes les exceptions dans une classe C# où les deux ctor et finalizer jeter des exceptions?

class Test : IDisposable { 
    public Test() { 
    throw new Exception("Exception in ctor"); 
    } 
    public void Dispose() { 
    throw new Exception("Exception in Dispose()"); 
    } 
    ~Test() { 
    this.Dispose(); 
    } 
} 

J'ai essayé, mais ça ne marche pas:

static void Main() { 
    Test t = null; 
    try { 
    t = new Test(); 
    } 
    catch (Exception ex) { 
    Console.Error.WriteLine(ex.Message); 
    } 

    // t is still null 
} 

J'ai aussi essayé d'utiliser « en utilisant » mais il ne gère pas l'exception levée de ~ Test();

static void Main() { 
    try { 
    using (Test t = new Test()) { } 
    } 
    catch (Exception ex) { 
    Console.Error.WriteLine(ex.Message); 
    } 
} 

Des idées comment puis-je contourner?

+0

Dans le second cas (pas d'utilisation), vous n'appelez jamais réellement Dispose(). C# est collecté, donc la classe ne sera pas réellement nettoyée jusqu'à ce que le GC décide qu'il est temps de nettoyer.Le GC n'appelle pas non plus Dispose (même si vous le définissez), c'est donc à vous d'utiliser 'using' ou appelez explicitement Dispose lorsque vous utilisez une classe IDisposable. –

+0

Merci pour votre réponse. En fait, la classe Test n'est pas écrite par moi et c'est simplement à des fins de démonstration, montrant une classe avec un comportement similaire qui est hors de mon contrôle. Le point clé est que, alors que le constructeur lance une exception, l'objet de Test est un semi-produit sans variables de référence. – Frank

Répondre

5

Tout d'abord, un Finalizer devrait jamais jeter une exception. Si c'est le cas, quelque chose est devenu catastrophique et l'application devrait planter dur. Un Finalizer ne devrait jamais appeler Dispose() directement. Les finaliseurs servent uniquement à libérer des ressources non gérées, car les ressources gérées peuvent ne pas être dans un état valide une fois que le Finalizer est exécuté. Les références gérées seront déjà nettoyées par le garbage collector, il vous suffit donc de les éliminer dans votre Dispose, pas dans votre Finalizer. Cela dit, une exception dans Dispose doit être interceptée si vous appelez Dispose explicitement. Je ne comprends pas très bien comment le cas 'using' n'a pas jeté l'exception. Cela dit, Dispose ne devrait pas non plus être une exception, si vous pouvez l'éviter. En particulier, un Dispose qui lance une exception après un bloc using écrase toute exception qui pourrait survenir dans le bloc using avec l'exception Dispose.


Certains documents de référence supplémentaires here

+0

Le problème est que si le constructeur lance une exception, je n'ai pas de pointeur vers l'objet "partial" donc je ne peux pas appeler le Dispose() explicitement. De plus, avec l'instruction "using", l'exception du constructeur est interceptée - mais lorsque GC appelle le finaliseur, il y a une autre exception qui ne peut pas être gérée. – Frank

+0

Si un objet lève une exception dans son constructeur après avoir partiellement alloué des ressources non managées, il s'agit alors d'un défaut de conception dans l'objet. Malheureusement, vous ne pouvez rien y faire. Si un objet devait absolument lancer une exception pendant la construction, une meilleure conception consisterait à «annuler» les allocations non gérées, si possible. Mieux encore, déplacez la logique qui pourrait échouer du constructeur vers une autre méthode Init. –

3

Je pense qu'une partie de la réponse est que vous ne devriez pas gérer les exceptions dans ces cas.

Vous ne devriez attraper des exceptions que si vous pouvez les récupérer, ou si vous pouvez ajouter des informations supplémentaires à l'exception et les relancer. Vous ne devriez pas attraper toutes les exceptions. Laissez le code plus haut dans la pile d'appels gérer autant d'exceptions que possible.

1

J'ai quelques observations. Tout d'abord, évitez de lancer des exceptions à partir de Dispose. En fait, j'irais presque jusqu'à dire jamais. Les développeurs .NET ont été conditionnés à s'attendre à ce que Dispose réussisse toujours et pour de bonnes raisons. Il serait maladroit d'emballer l'appel en essayant à chaque fois et cela réduirait certainement la lisibilité. Deuxièmement, et ceci fait l'objet d'un débat fréquent, évitez de lancer des exceptions aux constructeurs. Les exceptions liées à la validation d'un état tel que ArgumentException ou IndexOutOfRangeException sont correctes car elles sont généralement générées en raison de violations de pré-condition et sont généralement le résultat d'une erreur de programmation. Mais, des exceptions imprévisibles telles que SqlException forceraient un appelant à envelopper le constructeur dans un bloc try-catch. Encore une fois, cela conduit à des scénarios de codage maladroits. Mais, plus important encore, cela peut entraîner des fuites de ressources subtiles dans les scénarios où le constructeur alloue des ressources non gérées puis lève une exception avant de renvoyer la nouvelle instance à l'appelant. Sans la référence d'instance, l'appelant ne peut pas appeler Dispose.

Questions connexes