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.
fonte
DebuggerDisplayAttribute
faça com que se esforce um pouco mais.Respostas:
Atualizar:
Esse bug foi corrigido no Visual Studio 2015, atualização 2. Avise-me se você ainda estiver com problemas para avaliar o ToString nos valores de estrutura usando a Atualização 2 ou posterior.
Resposta original:
Você está enfrentando uma limitação conhecida de bug / design com o Visual Studio 2015 e chamando ToString em tipos de estrutura. Isso também pode ser observado quando se lida com
System.DateTimeSpan
.System.DateTimeSpan.ToString()
funciona nas janelas de avaliação com o Visual Studio 2013, mas nem sempre funciona em 2015.Se você estiver interessado nos detalhes de baixo nível, aqui está o que está acontecendo:
Para avaliar
ToString
, o depurador faz o que é conhecido como "avaliação de função". Em termos bastante simplificados, o depurador suspende todos os threads no processo, exceto o thread atual, altera o contexto do thread atual para aToString
função, define um ponto de interrupção de proteção oculto e permite que o processo continue. Quando o ponto de interrupção da proteção é atingido, o depurador restaura o processo ao seu estado anterior e o valor de retorno da função é usado para preencher a janela.Para oferecer suporte a expressões lambda, tivemos que reescrever completamente o CLR Expression Evaluator no Visual Studio 2015. Em um nível alto, a implementação é:
Por causa da execução de IL, o depurador está sempre lidando com uma mistura complicada de valores "reais" e "falsos". Valores reais realmente existem no processo que está sendo depurado. Valores falsos existem apenas no processo do depurador. Para implementar a semântica de estrutura adequada, o depurador sempre precisa fazer uma cópia do valor ao enviar um valor de estrutura para a pilha IL. O valor copiado não é mais um valor "real" e agora existe apenas no processo do depurador. Isso significa que, se precisarmos executar posteriormente a avaliação da função
ToString
, não poderemos, porque o valor não existe no processo. Para tentar obter o valor, precisamos emular a execução doToString
método. Embora possamos imitar algumas coisas, há muitas limitações. Por exemplo, não podemos emular código nativo e não podemos executar chamadas para delegar valores "reais" ou chamadas para valores de reflexão.Com tudo isso em mente, aqui está o que está causando os vários comportamentos que você está vendo:
NodaTime.Instant.ToString
-> Isso ocorre porque é do tipo struct e a implementação do ToString não pode ser emulada pelo depurador, conforme descrito acima.Thread.Sleep
parece levar tempo zero quando chamado porToString
uma estrutura -> Isso ocorre porque o emulador está sendo executadoToString
. Thread.Sleep é um método nativo, mas o emulador está ciente disso e simplesmente ignora a chamada. Fazemos isso para tentar obter um valor para mostrar ao usuário. Um atraso não seria útil neste caso.DisplayAttibute("ToString()")
trabalho. -> Isso é confuso. A única diferença entre a chamada implícita deToString
eDebuggerDisplay
é que qualquer tempo limite daToString
avaliação implícita desabilitará todas asToString
avaliações implícitas desse tipo até a próxima sessão de depuração. Você pode estar observando esse comportamento.Em termos de problema / bug de design, isso é algo que planejamos abordar em uma versão futura do Visual Studio.
Espero que isso esclareça as coisas. Deixe-me saber se você tem mais perguntas. :-)
fonte
innerResult
iniciar como nulo, o loop nunca será encerrado e, eventualmente, a avaliação expirará. De fato, as avaliações permitem apenas que um único encadeamento no processo seja executado por padrão; portanto, você verá o mesmo comportamento, independentemente de o emulador ser usado ou não.