Ambiente: Visual Studio 2015 RTM. (Eu não tentei versões mais antigas.)
Recentemente, depurei parte do meu código Noda Time e notei que, quando tenho uma variável local do tipo NodaTime.Instant
(um dos struct
tipos centrais no Noda Time), as janelas "Locals" e "Watch" parece não chamar sua ToString()
substituição. Se eu ligar ToString()
explicitamente na janela de inspeção, vejo a representação apropriada, mas caso contrário, vejo:
variableName {NodaTime.Instant}
o que não é muito útil.
Se eu alterar a substituição para retornar uma string constante, a string será exibida no depurador, para que seja claramente possível perceber que ela está lá - ela simplesmente não deseja usá-la em seu estado "normal".
Decidi reproduzir isso localmente em um pequeno aplicativo de demonstração, e aqui está o que eu criei. (Observe que em uma versão anterior deste post, DemoStruct
havia uma classe e DemoClass
não existia - minha culpa, mas explica alguns comentários que parecem estranhos agora ...)
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();
}
}
No depurador, agora vejo:
demoClass {DemoClass}
demoStruct {Struct: Bar}
No entanto, se eu reduzir a Thread.Sleep
chamada de 1 segundo para 900ms, ainda haverá uma breve pausa, mas vejo Class: Foo
como o valor. Parece que não importa quanto tempo a Thread.Sleep
chamada está DemoStruct.ToString()
, ela sempre é exibida corretamente - e o depurador exibe o valor antes que a suspensão tenha sido concluída. (É como se estivesse Thread.Sleep
desativado.)
Agora, Instant.ToString()
em Noda Time, trabalha bastante, mas certamente não leva um segundo inteiro - portanto, presumivelmente, há mais condições que fazem com que o depurador desista de avaliar uma ToString()
chamada. E é claro que é uma estrutura de qualquer maneira.
Eu tentei repetir para ver se é um limite de pilha, mas isso parece não ser o caso.
Então, como posso descobrir o que está impedindo a avaliação completa do VS Instant.ToString()
? Como observado abaixo, DebuggerDisplayAttribute
parece ajudar, mas sem saber o porquê , nunca ficarei totalmente confiante quando precisar e quando não precisar.
Atualizar
Se eu usar DebuggerDisplayAttribute
, as coisas mudam:
// For the sample code in the question...
[DebuggerDisplay("{ToString()}")]
public class DemoClass
me dá:
demoClass Evaluation timed out
Enquanto que quando eu o aplico no Noda Time:
[DebuggerDisplay("{ToString()}")]
public struct Instant
um aplicativo de teste simples mostra o resultado certo:
instant "1970-01-01T00:00:00Z"
Portanto, presumivelmente, o problema no Noda Time é uma condição que se DebuggerDisplayAttribute
impõe - mesmo que não exista nos tempos limite. (Isso estaria de acordo com minha expectativa de que Instant.ToString
é rápida o suficiente para evitar um tempo limite.)
Essa pode ser uma solução boa o suficiente - mas eu ainda gostaria de saber o que está acontecendo e se posso alterar o código simplesmente para evitar ter que colocar o atributo em todos os vários tipos de valor no Noda Time.
Curioso e curioso
O que quer que esteja confundindo, o depurador só o confunde às vezes. Vamos criar uma classe que detém um Instant
e usa-lo para seu próprio ToString()
método:
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();
}
}
Agora acabo vendo:
instant {NodaTime.Instant}
wrapper {1970-01-01T00:00:00Z}
No entanto, por sugestão de Eren nos comentários, se eu mudar InstantWrapper
para ser uma estrutura, recebo:
instant {NodaTime.Instant}
wrapper {InstantWrapper}
Portanto, ele pode avaliar Instant.ToString()
- desde que seja invocado por outro ToString
método ... que esteja dentro de uma classe. A parte da classe / estrutura parece ser importante com base no tipo da variável que está sendo exibida, não no código que precisa ser executado para obter o resultado.
Como outro exemplo disso, se usarmos:
object boxed = NodaConstants.UnixEpoch;
... então funciona bem, exibindo o valor certo. Cor me confuso.
DebuggerDisplayAttribute
faça com que se esforce um pouco mais.