Visual Studio 디버거가 ToString 재정의 평가를 중지하게 만드는 원인은 무엇입니까?


221

환경 : Visual Studio 2015 RTM. (이전 버전은 시도하지 않았습니다.)

최근에, 나는 일부 Noda Time 코드를 디버깅하고 있으며 , 지역 변수 유형 NodaTime.Instant( structNoda Time 의 중심 유형 중 하나 ), "Locals"및 "Watch"창을 발견했습니다. ToString()재정의 를 호출하지 않는 것 같습니다 . ToString()조사 식 창에서 명시 적으로 호출 하면 적절한 표현이 표시되지만 그렇지 않으면 다음과 같이 표시됩니다.

variableName       {NodaTime.Instant}

그다지 유용하지 않습니다.

상수 문자열을 반환하도록 재정의를 변경하면 문자열 디버거에 표시되므로 해당 문자열 분명히 있음을 알 수 있습니다. "정상"상태에서 사용하고 싶지 않습니다.

나는 이것을 작은 데모 앱에서 로컬로 재현하기로 결정했으며, 여기에 내가 생각해 낸 것이 있습니다. (이 게시물의 초기 버전 DemoStruct에서는 수업이었고 DemoClass전혀 존재하지 않았습니다-내 잘못이지만, 지금은 이상하게 보이는 몇 가지 의견을 설명합니다 ...)

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();
    }
}

디버거에서 이제 볼 수 있습니다.

demoClass    {DemoClass}
demoStruct   {Struct: Bar}

그러나 Thread.Sleep통화를 1 초에서 900ms로 줄이면 여전히 일시 정지가 발생하지만 Class: Foo값으로 간주합니다. Thread.Sleep호출 시간은 중요하지 않으며 DemoStruct.ToString()항상 올바르게 표시되며 디버거는 절전 모드가 완료되기 전에 값을 표시합니다. ( Thread.Sleep비활성화 된 것처럼 보입니다.)

이제 Instant.ToString()Noda Time에서는 상당한 양의 작업을 수행하지만 확실히 1 초가 걸리지 않으므로 디버거가 ToString()호출 평가를 포기하게하는 조건이 더 많이있을 수 있습니다 . 그리고 물론 그것은 구조입니다.

스택 제한인지 확인하기 위해 되풀이를 시도했지만 그럴 수는 없습니다.

그렇다면 VS가 완전히 평가하지 못하게하는 원인을 어떻게 해결할 수 Instant.ToString()있습니까? 아래에 언급했듯이 DebuggerDisplayAttribute도움이되는 것처럼 보이지만 이유 를 모르면 필요할 때와 필요하지 않을 때 완전히 확신 할 수는 없습니다.

최신 정보

를 사용 DebuggerDisplayAttribute하면 상황이 변경됩니다.

// For the sample code in the question...
[DebuggerDisplay("{ToString()}")]
public class DemoClass

나에게 준다 :

demoClass      Evaluation timed out

Noda Time에 신청할 때 :

[DebuggerDisplay("{ToString()}")]
public struct Instant

간단한 테스트 앱이 올바른 결과를 보여줍니다.

instant    "1970-01-01T00:00:00Z"

그래서 아마 노다 시간의 문제는 어떤 조건입니다 DebuggerDisplayAttribute 수행 을 통해 힘 -이 시간 제한을 통해 강제하지 않더라도. (이것은 Instant.ToString시간 초과를 피하기에 충분히 빠르다는 나의 기대와 일치 합니다.)

충분히 좋은 해결책이 될 수 -하지만, 난 여전히처럼 무슨 일이 일어나고 있는지 알고, 내가 노다 시간에 모든 다양한 값 형식에 속성을 넣어 것을 피하기 위해 단순히 코드를 변경할 수 있는지 여부하는 것입니다.

호기심과 호기심

디버거를 혼란스럽게 만드는 것은 때때로 혼란스럽게합니다. 의 클래스 만들어 보자 보유Instant하고 자신을 위해 그것을 사용하는 ToString()방법 :

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();
    }
}

이제 나는보고 :

instant    {NodaTime.Instant}
wrapper    {1970-01-01T00:00:00Z}

그러나 Eren의 의견에 따르면 InstantWrapper구조체로 변경 하면 다음과 같은 결과를 얻습니다.

instant    {NodaTime.Instant}
wrapper    {InstantWrapper}

따라서 클래스 내에서 다른 메소드에 의해 호출되는 한 평가할 수 있습니다 . 클래스 / 구조 부분은 결과를 얻기 위해 어떤 코드를 실행해야하는지가 아니라 표시되는 변수의 유형에 따라 중요해 보입니다.Instant.ToString()ToString

이것의 또 다른 예로, 다음을 사용하면 :

object boxed = NodaConstants.UnixEpoch;

... 그런 다음 제대로 작동하여 올바른 값을 표시합니다. 혼란스러워


7
VS 2013에서 @John의 동일한 동작 (c # 6 항목을 제거해야 함)과 함께 다음 메시지가 추가됩니다. 이름 함수 평가는 이전 함수 평가 시간이 초과되어 비활성화되었습니다. 기능 평가를 다시 사용하려면 실행을 계속해야합니다. 문자열
vc 74

1
c # 6.0 @ 3-14159265358979323846264
Neel

1
어쩌면 DebuggerDisplayAttribute조금 더 열심히 시도 할 수도 있습니다.
롤링

1
5 점 neelbhatt40.wordpress.com/2015/07/13/…을 참조하십시오. 새로운 c # 6.0을위한 @ 3-14159265358979323846264
Neel

5
@ DiomidisSpinellis : 글쎄, 나는 여기에 그것을 요청했다 .a) 전에 똑같은 것을 보았거나 VS 내부를 알고있는 사람이 대답 할 수있다. b) 미래에 같은 문제가 발생하면 누구나 신속하게 답변을 얻을 수 있습니다.
Jon Skeet

답변:


193

최신 정보:

이 버그는 Visual Studio 2015 업데이트 2에서 수정되었습니다. 업데이트 2 이상을 사용하여 구조체 값에서 ToString을 평가할 때 여전히 문제가 발생하는지 알려주세요.

원래 답변 :

Visual Studio 2015에서 알려진 버그 / 디자인 제한이 발생하고 구조체 유형에서 ToString을 호출하고 있습니다. 이 문제를 처리 할 때도 관찰 할 수 있습니다 System.DateTimeSpan. System.DateTimeSpan.ToString()Visual Studio 2013의 평가 창에서 작동하지만 2015에서 항상 작동하는 것은 아닙니다.

낮은 수준의 세부 정보에 관심이 있다면 다음과 같이 진행됩니다.

를 평가하기 ToString위해 디버거는 "함수 평가"기능을 수행합니다. 크게 단순화 된 용어로, 디버거는 현재 스레드를 제외한 프로세스의 모든 스레드를 일시 중단하고 현재 스레드의 컨텍스트를 ToString함수로 변경하고 숨겨진 가드 중단 점을 설정 한 다음 프로세스를 계속할 수 있습니다. 가드 중단 점에 도달하면 디버거가 프로세스를 이전 상태로 복원하고 함수의 리턴 값을 사용하여 창을 채 웁니다.

람다 식을 지원하려면 Visual Studio 2015에서 CLR Expression Evaluator를 완전히 다시 작성해야했습니다. 높은 수준에서 구현은 다음과 같습니다.

  1. Roslyn은 다양한 검사 창에 표시 될 값을 얻기 위해 표현식 / 지역 변수에 대한 MSIL 코드를 생성합니다.
  2. 디버거는 IL을 해석하여 결과를 얻습니다.
  3. "호출"명령이있는 경우 디버거는 위에서 설명한 기능 평가를 실행합니다.
  4. 디버거 / roslyn은이 결과를 가져와 사용자에게 표시되는 트리와 같은보기로 형식화합니다.

IL의 실행으로 인해 디버거는 항상 "실제"와 "가짜"값의 복잡한 혼합을 처리합니다. 실제 값은 실제로 디버깅중인 프로세스에 존재합니다. 가짜 값은 디버거 프로세스에만 존재합니다. 적절한 구조체 의미론을 구현하기 위해 디버거는 구조체 값을 IL 스택으로 푸시 할 때 항상 값의 복사본을 만들어야합니다. 복사 된 값은 더 이상 "실제"값이 아니며 이제 디버거 프로세스에만 존재합니다. 즉, 나중에의 함수 평가를 수행해야하는 경우 ToString프로세스에 값이 없기 때문에 수행 할 수 없습니다. 시도하고 가치를 얻으려면 우리는 실행을 모방해야합니다.ToString방법. 우리는 어떤 것을 흉내낼 수 있지만 많은 한계가 있습니다. 예를 들어 네이티브 코드를 에뮬레이션 할 수 없으며 "실제"델리게이트 값에 대한 호출 또는 리플렉션 값에 대한 호출을 실행할 수 없습니다.

이 모든 것을 염두에두고, 당신이보고있는 다양한 행동을 일으키는 원인은 다음과 같습니다.

  1. 디버거가 평가하지 않습니다 NodaTime.Instant.ToString-> 이것은 구조체 유형이므로 위에서 설명한대로 디버거로 ToString 구현을 에뮬레이션 할 수 없기 때문입니다.
  2. Thread.SleepToString구조체에서 호출 할 때 시간이 걸리는 것 같습니다 -> 에뮬레이터가 실행 중이기 때문 ToString입니다. Thread.Sleep은 기본 메소드이지만 에뮬레이터가 인식하고 호출을 무시합니다. 우리는 사용자에게 보여줄 가치를 얻으려고 노력합니다. 이 경우 지연이 도움이되지 않습니다.
  3. DisplayAttibute("ToString()")공장. -> 혼란 스럽습니다. 암시 적 호출 ToString과 암시 적 호출의 유일한 차이점은 암시 DebuggerDisplayToString 평가 시간이 초과 ToString되면 다음 디버그 세션까지 해당 유형에 대한 모든 암시 적 평가가 비활성화됩니다 . 당신은 그 행동을 관찰하고있을 수 있습니다.

디자인 문제 / 버그의 관점에서 이것은 Visual Studio의 향후 릴리스에서 해결하려고하는 것입니다.

잘만되면 그것은 일을 정리합니다. 더 궁금한 점이 있으면 알려주세요. :-)


1
구현이 "문자열 리터럴 반환"인 경우 Instant.ToString의 작동 방식에 대한 아이디어가 있습니까? 아직 설명되지 않은 일부 복잡성이있는 것 같습니다.) 나는 그 동작을 실제로 재현 할 수 있는지 확인할 것입니다 ...
Jon Skeet

1
@Jon, 나는 당신이 무엇을 요구하는지 잘 모르겠습니다. 디버거는 실제 함수 평가를 수행 할 때 구현과 무관하며 항상 먼저 시도합니다. 디버거는 호출을 에뮬레이션해야 할 때만 구현에 관심을 갖습니다. 문자열 리터럴을 반환하는 것이 가장 간단한 에뮬레이션입니다.
패트릭 넬슨

8
이상적으로 우리는 CLR이 모든 것을 실행하기를 원합니다. 가장 정확하고 안정적인 결과를 제공합니다. 이것이 ToString 호출에 대한 실제 함수 평가를 수행하는 이유입니다. 이것이 불가능할 때, 우리는 다시 전화를 모방합니다. 즉, 디버거가 메서드를 실행하는 CLR 인 것처럼 가장합니다. 분명히 구현이 <code> "Hello"를 반환 </ code>하면 쉽게 수행 할 수 있습니다. 구현이 P-Invoke를 수행하면 더 어렵거나 불가능합니다.
패트릭 넬슨

3
@tzachs, 에뮬레이터는 완전히 단일 스레드입니다. 경우 innerResult시작 널 (null)로 떨어져, 루프는 종료하지 않습니다 결국 평가는 시간이 초과됩니다. 실제로 평가는 프로세스에서 단일 스레드 만 기본적으로 실행되도록 허용하므로 에뮬레이터의 사용 여부에 관계없이 동일한 동작을 볼 수 있습니다.
패트릭 넬슨

2
BTW, 평가에 여러 스레드가 필요하다는 것을 안다면 Debugger.NotifyOfCrossThreadDependency를 살펴보십시오 . 이 메소드를 호출하면 평가에 모든 스레드를 실행해야한다는 메시지와 함께 평가가 중단되고 디버거는 사용자가 강제로 평가를 수행 할 수있는 버튼을 제공합니다. 단점은 평가 중에 다른 스레드에서 발생한 중단 점이 무시된다는 것입니다.
패트릭 넬슨
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.