Environnement: Visual Studio 2015 RTM. (Je n'ai pas essayé d'anciennes versions.)
Récemment, j'ai débogué une partie de mon code Noda Time , et j'ai remarqué que lorsque j'ai une variable locale de type NodaTime.Instant
(l'un des struct
types centraux de Noda Time), les fenêtres "Locals" et "Watch" ne semblent pas appeler son ToString()
remplacement. Si j'appelle ToString()
explicitement dans la fenêtre de surveillance, je vois la représentation appropriée, mais sinon je vois juste:
variableName {NodaTime.Instant}
ce qui n'est pas très utile.
Si je modifie le remplacement pour renvoyer une chaîne constante, la chaîne est affichée dans le débogueur, il est donc clairement capable de détecter qu'elle est là - il ne veut tout simplement pas l'utiliser dans son état "normal".
J'ai décidé de reproduire cela localement dans une petite application de démonstration, et voici ce que j'ai trouvé. (Notez que dans une première version de ce post, DemoStruct
c'était une classe et DemoClass
n'existait pas du tout - ma faute, mais cela explique certains commentaires qui semblent bizarres maintenant ...)
using System;
using System.Diagnostics;
using System.Threading;
public struct DemoStruct
{
public string Name { get; }
public DemoStruct(string name)
{
Name = name;
}
public override string ToString()
{
Thread.Sleep(1000); // Vary this to see different results
return $"Struct: {Name}";
}
}
public class DemoClass
{
public string Name { get; }
public DemoClass(string name)
{
Name = name;
}
public override string ToString()
{
Thread.Sleep(1000); // Vary this to see different results
return $"Class: {Name}";
}
}
public class Program
{
static void Main()
{
var demoClass = new DemoClass("Foo");
var demoStruct = new DemoStruct("Bar");
Debugger.Break();
}
}
Dans le débogueur, je vois maintenant:
demoClass {DemoClass}
demoStruct {Struct: Bar}
Cependant, si je Thread.Sleep
réduis l' appel de 1 seconde à 900 ms, il y a encore une courte pause, mais je vois Class: Foo
la valeur. Peu importe la durée de l' Thread.Sleep
appel DemoStruct.ToString()
, il s'affiche toujours correctement - et le débogueur affiche la valeur avant la fin du sommeil. (C'est comme si elle Thread.Sleep
était désactivée.)
Maintenant, Instant.ToString()
dans Noda, le temps fait beaucoup de travail, mais cela ne prend certainement pas une seconde entière - il y a donc probablement plus de conditions qui poussent le débogueur à abandonner l'évaluation d'un ToString()
appel. Et bien sûr, c'est une structure de toute façon.
J'ai essayé de répéter pour voir s'il s'agit d'une limite de pile, mais cela ne semble pas être le cas.
Alors, comment puis-je déterminer ce qui empêche VS d'évaluer complètement Instant.ToString()
? Comme indiqué ci-dessous, DebuggerDisplayAttribute
semble aider, mais sans savoir pourquoi , je ne serai jamais entièrement confiant quand j'en ai besoin et quand je n'en ai pas.
Mettre à jour
Si j'utilise DebuggerDisplayAttribute
, les choses changent:
// For the sample code in the question...
[DebuggerDisplay("{ToString()}")]
public class DemoClass
Donne moi:
demoClass Evaluation timed out
Alors que lorsque je l'applique dans Noda Time:
[DebuggerDisplay("{ToString()}")]
public struct Instant
une simple application de test me montre le bon résultat:
instant "1970-01-01T00:00:00Z"
On peut donc supposer le problème dans le temps Noda est une condition qui DebuggerDisplayAttribute
fait la force à travers - même si elle ne force pas par les délais d' attente. (Ce serait conforme à mon attente, qui Instant.ToString
est facilement assez rapide pour éviter un délai d'attente.)
Cela peut être une assez bonne solution - mais j'aimerais toujours savoir ce qui se passe et si je peux changer le code simplement pour éviter d'avoir à mettre l'attribut sur tous les différents types de valeurs dans Noda Time.
Curieux et curieux
Tout ce qui prête à confusion, le débogueur ne fait que le confondre parfois. Créons une classe qui contient un Instant
et l'utilise pour sa propre ToString()
méthode:
using NodaTime;
using System.Diagnostics;
public class InstantWrapper
{
private readonly Instant instant;
public InstantWrapper(Instant instant)
{
this.instant = instant;
}
public override string ToString() => instant.ToString();
}
public class Program
{
static void Main()
{
var instant = NodaConstants.UnixEpoch;
var wrapper = new InstantWrapper(instant);
Debugger.Break();
}
}
Maintenant je finis par voir:
instant {NodaTime.Instant}
wrapper {1970-01-01T00:00:00Z}
Cependant, à la suggestion d'Eren dans les commentaires, si je change InstantWrapper
pour être une structure, j'obtiens:
instant {NodaTime.Instant}
wrapper {InstantWrapper}
Donc, il peut évaluer Instant.ToString()
- tant qu'il est invoqué par une autre ToString
méthode ... qui est dans une classe. La partie class / struct semble être importante en fonction du type de la variable affichée, et non du code à exécuter pour obtenir le résultat.
Comme autre exemple de cela, si nous utilisons:
object boxed = NodaConstants.UnixEpoch;
... alors cela fonctionne très bien, affichant la bonne valeur. Colorie-moi confus.
la source
DebuggerDisplayAttribute
ferait essayer un peu plus.Réponses:
Mettre à jour:
Ce bogue a été corrigé dans Visual Studio 2015 Update 2. Faites-moi savoir si vous rencontrez toujours des problèmes pour évaluer ToString sur les valeurs de structure à l'aide de Update 2 ou version ultérieure.
Réponse originale:
Vous rencontrez une limitation de conception / bogue connue avec Visual Studio 2015 et appelez ToString sur les types de structure. Cela peut également être observé lors du traitement
System.DateTimeSpan
.System.DateTimeSpan.ToString()
fonctionne dans les fenêtres d'évaluation avec Visual Studio 2013, mais ne fonctionne pas toujours en 2015.Si vous êtes intéressé par les détails de bas niveau, voici ce qui se passe:
Pour évaluer
ToString
, le débogueur fait ce qu'on appelle «l'évaluation de fonction». En termes très simplifiés, le débogueur suspend tous les threads du processus à l'exception du thread actuel, modifie le contexte du thread actuel enToString
fonction, définit un point d'arrêt de garde masqué, puis permet au processus de continuer. Lorsque le point d'arrêt de garde est atteint, le débogueur restaure le processus à son état précédent et la valeur de retour de la fonction est utilisée pour remplir la fenêtre.Pour prendre en charge les expressions lambda, nous avons dû réécrire complètement l'évaluateur d'expression CLR dans Visual Studio 2015. À un niveau élevé, l'implémentation est:
En raison de l'exécution d'IL, le débogueur est toujours confronté à un mélange compliqué de valeurs "réelles" et "fausses". Des valeurs réelles existent réellement dans le processus en cours de débogage. Les fausses valeurs n'existent que dans le processus de débogage. Pour implémenter une sémantique de structure appropriée, le débogueur doit toujours faire une copie de la valeur lors de la transmission d'une valeur de structure à la pile IL. La valeur copiée n'est plus une valeur "réelle" et n'existe désormais que dans le processus de débogage. Cela signifie que si nous devons ultérieurement effectuer une évaluation de la fonction de
ToString
, nous ne pouvons pas, car la valeur n'existe pas dans le processus. Pour essayer d'obtenir la valeur dont nous avons besoin pour émuler l'exécution de laToString
méthode. Bien que nous puissions émuler certaines choses, il existe de nombreuses limitations. Par exemple, nous ne pouvons pas émuler de code natif et nous ne pouvons pas exécuter des appels à des valeurs de délégué "réelles" ou des appels à des valeurs de réflexion.Avec tout cela à l'esprit, voici ce qui cause les divers comportements que vous voyez:
NodaTime.Instant.ToString
-> C'est parce qu'il est de type struct et l'implémentation de ToString ne peut pas être émulée par le débogueur comme décrit ci-dessus.Thread.Sleep
semble ne prendre aucun temps lorsqu'il est appelé parToString
une structure -> C'est parce que l'émulateur est en cours d'exécutionToString
. Thread.Sleep est une méthode native, mais l'émulateur en est conscient et ignore simplement l'appel. Nous faisons cela pour essayer d'obtenir une valeur à montrer à l'utilisateur. Un retard ne serait pas utile dans ce cas.DisplayAttibute("ToString()")
travaux. -> C'est déroutant. La seule différence entre l'appel implicite deToString
etDebuggerDisplay
est que tout délai d'expiration de l'ToString
évaluation implicite désactivera toutes lesToString
évaluations implicites pour ce type jusqu'à la prochaine session de débogage. Vous observez peut-être ce comportement.En ce qui concerne le problème / bug de conception, c'est quelque chose que nous prévoyons de résoudre dans une future version de Visual Studio.
Espérons que cela clarifie les choses. Dites moi si vous avez d'autres questions. :-)
la source
innerResult
démarre comme nul, la boucle ne se terminera jamais et finalement l'évaluation expirera. En fait, les évaluations n'autorisent qu'un seul thread dans le processus à s'exécuter par défaut, vous verrez donc le même comportement, que l'émulateur soit utilisé ou non.