web-dev-qa-db-ja.com

Visual StudioデバッガーがToStringオーバーライドの評価を停止するのはなぜですか?

環境:Visual Studio 2015 RTM。 (古いバージョンは試していません。)

最近、私は Noda Time コードのいくつかをデバッグしており、タイプNodaTime.Instant(中心的なstructタイプの1つ)のローカル変数を取得すると、野田時間)、「Locals」および「Watch」ウィンドウは、ToString()オーバーライドを呼び出すように表示されません。ウォッチウィンドウでToString()を明示的に呼び出すと、適切な表現が表示されますが、そうでない場合は次のように表示されます。

variableName       {NodaTime.Instant}

これはあまり役に立ちません。

オーバーライドを変更して定数文字列を返すと、文字列isがデバッガに表示されるため、そこにあることを明確に把握できます。「通常の」 「状態。

私はこれを小さなデモアプリでローカルに再現することにしました。これが私が思いついたものです。 (この投稿の初期バージョンでは、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が無効になっているようです。)

これで、Noda TimeのInstant.ToString()はかなりの量の作業を行いますが、確かに1秒もかかりません。したがって、おそらくToString()呼び出しの評価をあきらめる条件がさらにあると思われます。そしてもちろん、それはとにかく構造体です。

スタック制限かどうかを確認するために再帰を試みましたが、そうではないようです。

それでは、VSがInstant.ToString()を完全に評価するのを妨げているものをどうやって解決できますか?以下に示すように、DebuggerDisplayAttributeが役立つように見えますが、whyがわからなくても、必要なときとそうでないときを完全に確信することはできません。

更新

DebuggerDisplayAttribute を使用すると、状況が変わります:

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

私に与えます:

demoClass      Evaluation timed out

野田タイムで適用する場合:

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

簡単なテストアプリで正しい結果が表示されます。

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

おそらく、野田時間の問題は、DebuggerDisplayAttributedoes強制的に-タイムアウトを強制しなくても、強制的に強制するという何らかの条件です。 (これは、Instant.ToStringがタイムアウトを回避するのに十分な速さであるという私の期待に沿ったものです。)

これはmay十分なソリューションです-しかし、私はまだ何が起こっているのか、そしてNodaのすべてのさまざまな値タイプに属性を置く必要がないようにコードを変更できるかどうかを知りたい時間。

好奇心と好奇心

デバッガを混乱させるものは何でも、それを混乱させるだけです。 holdsInstantであり、それを独自の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}

can evaluate Instant.ToString()-クラス内にある別のToStringメソッドによって呼び出される限り...クラス/構造部分は、結果を取得するために実行する必要があるコードではなく、表示される変数のタイプに基づいて重要であると思われます。

この別の例として、次を使用する場合:

object boxed = NodaConstants.UnixEpoch;

...その後、正常に機能し、適切な値が表示されます。私を混乱させてください。

218
Jon Skeet

更新:

このバグはVisual Studio 2015 Update 2で修正されました。Update2以降を使用して、構造体の値で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.Sleepは、構造体でToStringによって呼び出されたときにゼロ時間がかかるようです->これは、エミュレータがToStringを実行しているためです。 Thread.Sleepはネイティブメソッドですが、エミュレータはそれを認識しており、呼び出しを無視します。これを実行して、ユーザーに表示する値を取得しようとします。この場合、遅延は役に立たないでしょう。
  3. DisplayAttibute("ToString()")は機能します。 ->それはわかりにくいです。 ToStringDebuggerDisplayの暗黙的な呼び出しの唯一の違いは、暗黙的なToString評価のタイムアウトにより、次のタイプまでのすべての暗黙的なToString評価が無効になることです。デバッグセッション。その動作を観察している可能性があります。

設計の問題/バグに関しては、これはVisual Studioの将来のリリースで対処する予定の何かです。

うまくいけば、これで事態は解決します。さらに質問がある場合はお知らせください。 :-)

191