Jeter VS rethrow: même résultat?
-
30-09-2019 - |
Question
à beaucoup se référant de la documentation sur le net, en particulier sur le SO, par exemple: Quelle est la bonne façon de re-jeter une exception en C #? il devrait y avoir une différence entre « un jet e; » et "jeter;".
Mais, à partir de: http://bartdesmet.net /blogs/bart/archive/2006/03/12/3815.aspx ,
ce code:
using System;
class Ex
{
public static void Main()
{
//
// First test rethrowing the caught exception variable.
//
Console.WriteLine("First test");
try
{
ThrowWithVariable();
}
catch (Exception ex)
{
Console.WriteLine(ex.StackTrace);
}
//
// Second test performing a blind rethrow.
//
Console.WriteLine("Second test");
try
{
ThrowWithoutVariable();
}
catch (Exception ex)
{
Console.WriteLine(ex.StackTrace);
}
}
private static void BadGuy()
{
//
// Some nasty behavior.
//
throw new Exception();
}
private static void ThrowWithVariable()
{
try
{
BadGuy();
}
catch (Exception ex)
{
throw ex;
}
}
private static void ThrowWithoutVariable()
{
try
{
BadGuy();
}
catch
{
throw;
}
}
}
donne le résultat suivant:
$ /cygdrive/c/Windows/Microsoft.NET/Framework/v4.0.30319/csc.exe Test.cs
Microsoft (R) Visual C# 2010 Compiler version 4.0.30319.1
Copyright (C) Microsoft Corporation. All rights reserved.
$ ./Test.exe
First test
at Ex.ThrowWithVariable()
at Ex.Main()
Second test
at Ex.ThrowWithoutVariable()
at Ex.Main()
qui est en totale contradiction avec le billet de blog.
Le même genre de résultat est obtenu avec le code de: http://crazorsharp.blogspot.com/2009/08/rethrowing-exception-without-resetting.html
Question originale : ce que je fais mal
UPDATE : même résultat avec .Net 3.5 / csc.exe 3.5.30729.4926
SUMUP :. Toutes vos réponses ont été formidables, merci encore
Ainsi, la raison est inline efficacement à cause des vacillements 64 bits.
Je devais choisir une seule réponse, et voici pourquoi je l'ai choisi LukeH réponse:
-
il devina le problème inline et le fait qu'il peut être lié à mon architecture 64 bits,
-
il a fourni le drapeau NoInlining qui est la façon la plus simple d'éviter ce comportement.
Toutefois, cette question s'élève maintenant une autre question: est-ce compatible comportement avec toutes les spécifications .Net: les CLR et ceux du langage de programmation C #
UPDATE : cette optimisation semble conforme selon la: Jeter VS rethrow: même résultat (merci 0xA3 )
Merci d'avance pour votre aide.
La solution
Je ne peux pas reproduire le problème -. En utilisant .NET 3.5 (32 bits) me donne les mêmes résultats décrits dans l'article de Bart
Je suppose que le compilateur / gigue .NET 4 - ou peut-être est le compilateur 64 bits / gigue si cela se passe sous 3,5 trop - est inline la méthode BadGuy
dans les méthodes d'appel. Essayez d'ajouter MethodImpl
attribut à BadGuy
et voir si cela fait une différence:
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
private static void BadGuy()
{
//
// Some nasty behavior.
//
throw new Exception();
}
Autres conseils
J'ai essayé d'exécuter ce code moi-même et les travaux de construction de débogage comme je m'y attendais, mais je suis arrivé le même résultat que vous dans la version release.
Je soupçonne que ce qui se passe est que le compilateur inline a simplement remplacé l'appel avec un jet new Exception();
BadGuy () parce que c'est la seule déclaration BadGuy ().
Si vous désactivez l'option « code Optimize » dans les propriétés du projet -.> Écran Générer, puis à la fois la construction des rejets et de débogage produit le même résultat qui montre Badguy () en haut de la trace de la pile
Il semble que les JIT optimiseurs fait un travail ici. Comme vous pouvez le voir, la pile d'appels dans le second cas est différent de celui dans le premier cas lorsque vous exécutez la construction de débogage. Cependant, dans la construction de presse, les deux piles d'appels sont identiques en raison de l'optimisation.
Pour voir que cela est lié à la gigue, vous pouvez décorer les méthodes avec MethodImplAttribute
attribut:
[MethodImpl(MethodImplOptions.NoOptimization)]
private static void ThrowWithoutVariable()
{
try
{
BadGuy();
}
catch
{
throw;
}
}
Notez que l'IL est encore différent pour ThrowWithoutVariable
et ThrowWithVariable
:
.method private hidebysig static void ThrowWithVariable() cil managed
{
// Code size 11 (0xb)
.maxstack 1
.locals init ([0] class [mscorlib]System.Exception ex)
.try
{
IL_0000: call void Ex::BadGuy()
IL_0005: leave.s IL_000a
} // end .try
catch [mscorlib]System.Exception
{
IL_0007: stloc.0
IL_0008: ldloc.0
IL_0009: throw
} // end handler
IL_000a: ret
} // end of method Ex::ThrowWithVariable
.method private hidebysig static void ThrowWithoutVariable() cil managed
{
// Code size 11 (0xb)
.maxstack 1
.try
{
IL_0000: call void Ex::BadGuy()
IL_0005: leave.s IL_000a
} // end .try
catch [mscorlib]System.Object
{
IL_0007: pop
IL_0008: rethrow
} // end handler
IL_000a: ret
} // end of method Ex::ThrowWithoutVariable
Mise à jour pour répondre à votre question de suivi que ce soit conforme à la spécification CLI
En fait, il conforme, à savoir permettre au compilateur JIT pour activer les optimisations importantes. Annexe F des états de la page 52 (d'accentuation par moi):
Certaines instructions du CIL exécutent implicites contrôles d'exécution qui assurent la mémoire et sécurité de type. À l'origine, la CLI garantis que les exceptions étaient précis , ce qui signifie que l'état du programme a été préservée quand une exception est jeté. Toutefois, l'application précise exceptions pour les contrôles implicites marques certaines optimisations importantes pratiquement impossible à appliquer. Les programmeurs peuvent maintenant déclarer, via un attribut personnalisé, qu'une méthode est « Détendu », qui dit que les exceptions résultant de contrôles d'exécution implicites n'a pas besoin d'être précis.
Relaxed contrôles préserver la vérifiabilité (en préservant la mémoire et la sécurité de type), tandis que permettant des optimisations qui Réorganiser instructions. En particulier, permet aux optimisations suivantes:
- Hisser contrôles d'exécution implicite sur des boucles.
- itérations de boucle Réorganiser (Par exemple, la vectorisation automatique et multithreading)
- boucles Interchangeabilité
- Inlining qui fait un inline Procédé moins aussi rapide que le macro équivalent
Utilisez une version de débogage et vous verrez la différence plus clairement. Avec une version de débogage la première exécution affichera l'emplacement de la ligne de throw ex
et le second comme provenant de l'appel réel à BadGuy
. Il est évident que le « problème » est l'appel à BadGuy -. Pas la ligne ex throw et vous chasser moins de fantômes avec l'instruction throw;
directe
Dans une pile trace ce faible profondeur les avantages ne sont pas aussi évidentes immediatley, dans une pile très profonde vous masque la source réelle du problème et perdre une certaine fidélité en lançant manuellement exception au lieu d'utiliser le construit en re-jeter déclaration.
Sur une note de côté, j'ai trouvé un hack posté sur un blog une fois (je l'ai depuis perdu la référence) qui vous permet de conserver l'appel pile sur rethrow. Cela est surtout utile si vous attrapez une exception dans un contexte (par exemple, dans un thread en cours d'exécution d'une opération asynchrone) et que vous voulez qu'il réémettre dans un autre (par exemple, dans l'autre thread qui a lancé l'opération asynchrone). Il utilise certaines fonctionnalités non documentées inclus pour permettre la conservation des traces de la pile à travers les frontières Remoting.
//This terrible hack makes sure track trace is preserved if exception is re-thrown
internal static Exception AppendStackTrace(Exception ex)
{
//Fool CLR into appending stack trace information when the exception is re-thrown
var remoteStackTraceString = typeof(Exception).GetField("_remoteStackTraceString",
BindingFlags.Instance |
BindingFlags.NonPublic);
if (remoteStackTraceString != null)
remoteStackTraceString.SetValue(ex, ex.StackTrace + Environment.NewLine);
return ex;
}