1. ホーム
  2. c#

[解決済み】Visual StudioデバッガがToStringオーバーライドの評価を停止する理由は何ですか?

2022-04-08 04:29:27

質問

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

最近、いくつかのデバッグで 野田時間 のコードで、ローカル変数に NodaTime.Instant (中央の struct のタイプは、quot;Locals" と "Watch" のウィンドウは、その ToString() をオーバーライドします。もし私が ToString() をウォッチウィンドウで明示的に表示すると、適切な表現が表示されますが、そうでない場合は、ただ表示されるだけです。

variableName       {NodaTime.Instant}

というのはあまり意味がない。

オーバーライドで定数文字列を返すように変更すると、文字列が デバッガに表示されるので、それがそこにあることは明らかにわかりますが、quot;normal" の状態では使いたくないだけです。

これをローカルに小さなデモアプリで再現してみたところ、以下のようになりました。(この投稿の初期バージョンでは 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秒から900ミリ秒に短縮すると、まだ短い間がありますが、その後に Class: Foo を値とします。の長さは関係ないようです。 Thread.Sleep の呼び出しは DemoStruct.ToString() そして、デバッガはスリープが完了する前にその値を表示します。(それはあたかも Thread.Sleep が無効になっています)。

現在 Instant.ToString() を評価するのをあきらめるような条件は、もっとたくさんあると思われます。 ToString() を呼び出します。もちろん、どうせ構造体だしね。

スタック制限なのかどうか再帰してみましたが、そうではないようです。

では、何が原因でVSの完全な評価ができないのかを調べるには、どうすればよいのでしょうか? Instant.ToString() ? 下記の通りです。 DebuggerDisplayAttribute は役に立ちそうですが、知らないうちに なぜ 必要なときと不要なときとで、完全に使い分けができるわけではありません。

更新情報

を使用する場合 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"

つまり、野田時間の問題は、以下のような条件だと思われます。 DebuggerDisplayAttribute する を強制終了させます。タイムアウトを強制終了させないのに。(これは、私が期待している Instant.ToString は、タイムアウトを回避するのに十分な速度が簡単に得られます)。

これは かもしれない しかし、何が起こっているのか、そして、Noda Timeの様々な値タイプすべてに属性を付ける必要がないように、単純にコードを変更することができるのか、知りたいのです。

ますます気になる

デバッガーを混乱させるものは何でも、時々混乱させるだけです。では、次のようなクラスを作ってみよう。 ホールド an 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;

...すると、正しい値が表示され、正常に動作します。私は混乱しています。

解決方法は?

アップデートしてください。

この不具合は、Visual Studio 2015 Update 2で修正されました。 Update 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. call"命令がある場合、デバッガは以下の処理を実行します。 のように関数を評価します。
  4. デバッガ/ロスリンは、この結果を受け取り、それをフォーマットして ツリー状に表示し、ユーザーに見せる。

デバッガーはILを実行するため、常に本物と偽物が混在した複雑な値を扱っています。 リアルな値は、デバッグ対象のプロセス内に実際に存在します。 偽の値はデバッガー プロセスにのみ存在します。 適切な構造体セマンティクスを実装するために、デバッガーは構造体の値を IL スタックにプッシュするときに、常に値のコピーを作成する必要があります。 コピーされた値は、もはや本物の値ではなく、デバッガープロセス内にのみ存在します。 つまり、後で ToString というのは、その値がプロセス内に存在しないからです。 この値を取得するためには ToString メソッドを使用します。 エミュレートできることもありますが、多くの制限があります。 例えば、ネイティブコードをエミュレートすることはできませんし、"real" デリゲート値への呼び出しやReflection値への呼び出しを実行することはできないのです。

それらを踏まえて、あなたが見ているさまざまな動作の原因を考えてみましょう。

  1. デバッガーは NodaTime.Instant.ToString -> これは 構造体型であり、ToString の実装は は、上記のようにデバッガによってエミュレートされます。
  2. Thread.Sleep によって呼び出された場合、時間はゼロになるようです。 ToString を搭載しています。 struct -> これは、エミュレータが実行する ToString . Thread.Sleepはネイティブメソッドですが、エミュレータはそれを認識しています。 を呼び出すと、それを無視します。 これは、値を取得しようとするために行うものです。 を表示させることができます。 この場合、遅延は役に立ちません。
  3. DisplayAttibute("ToString()") の作品です。->それは紛らわしいですね。 唯一 の暗黙の呼び出しの違いは ToStringDebuggerDisplay のタイムアウトは、暗黙的な ToString を評価すると、すべての暗黙の ToString

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

これですっきりしたかな。 また質問があれば教えてください :-)