Lingkungan: Visual Studio 2015 RTM. (Saya belum mencoba versi yang lebih lama.)
Baru-baru ini, saya telah men-debug beberapa kode Waktu Noda saya , dan saya perhatikan bahwa ketika saya mendapatkan variabel jenis lokal NodaTime.Instant
(salah satu struct
jenis utama dalam Waktu Noda), jendela "Lokal" dan "Tonton" tampaknya tidak memanggil ToString()
penggantiannya. Jika saya menelepon ToString()
secara eksplisit di jendela arloji, saya melihat representasi yang sesuai, tetapi sebaliknya saya hanya melihat:
variableName {NodaTime.Instant}
yang sangat tidak berguna.
Jika saya mengubah override untuk mengembalikan string konstan, string yang ditampilkan dalam debugger, sehingga jelas mampu mengambil bahwa itu ada - itu hanya tidak ingin menggunakannya dalam state "normal".
Saya memutuskan untuk mereproduksi ini secara lokal di aplikasi demo kecil, dan inilah yang saya buat. (Perhatikan bahwa dalam versi awal posting ini, DemoStruct
adalah sebuah kelas dan DemoClass
tidak ada sama sekali - salahku, tapi itu menjelaskan beberapa komentar yang terlihat aneh sekarang ...)
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();
}
}
Di debugger, sekarang saya melihat:
demoClass {DemoClass}
demoStruct {Struct: Bar}
Namun, jika saya mengurangi Thread.Sleep
panggilan turun dari 1 detik menjadi 900 ms, masih ada jeda singkat, tetapi kemudian saya melihat Class: Foo
sebagai nilainya. Tampaknya tidak masalah berapa lama Thread.Sleep
panggilan itu masuk DemoStruct.ToString()
, selalu ditampilkan dengan benar - dan debugger menampilkan nilai sebelum tidur selesai. (Seolah-olah Thread.Sleep
dinonaktifkan.)
Sekarang Instant.ToString()
di Noda Time melakukan cukup banyak pekerjaan, tetapi tentu saja tidak memakan waktu satu detik - jadi mungkin ada lebih banyak kondisi yang menyebabkan debugger menyerah mengevaluasi ToString()
panggilan. Dan tentu saja itu adalah struct.
Saya sudah mencoba berulang-ulang untuk melihat apakah itu batas tumpukan, tetapi tampaknya tidak demikian.
Jadi, bagaimana saya bisa mengetahui apa yang menghentikan VS dari sepenuhnya mengevaluasi Instant.ToString()
? Seperti disebutkan di bawah ini, DebuggerDisplayAttribute
tampaknya membantu, tetapi tanpa tahu mengapa , saya tidak akan pernah sepenuhnya percaya diri ketika saya membutuhkannya dan ketika saya tidak.
Memperbarui
Jika saya gunakan DebuggerDisplayAttribute
, semuanya berubah:
// For the sample code in the question...
[DebuggerDisplay("{ToString()}")]
public class DemoClass
memberi saya:
demoClass Evaluation timed out
Sedangkan ketika saya menerapkannya dalam Noda Time:
[DebuggerDisplay("{ToString()}")]
public struct Instant
aplikasi tes sederhana menunjukkan kepada saya hasil yang tepat:
instant "1970-01-01T00:00:00Z"
Jadi mungkin masalah di Noda Waktu adalah beberapa kondisi yang DebuggerDisplayAttribute
tidak berlaku melalui - meskipun tidak memaksa melalui timeout. (Ini akan sesuai dengan harapan saya yang Instant.ToString
cukup cepat untuk menghindari batas waktu.)
Ini mungkin solusi yang cukup bagus - tetapi saya masih ingin tahu apa yang terjadi, dan apakah saya dapat mengubah kode hanya untuk menghindari keharusan meletakkan atribut pada semua jenis nilai yang berbeda di Noda Time.
Ingin tahu dan ingin tahu
Apa pun yang membingungkan, debugger terkadang hanya membingungkannya. Mari kita membuat sebuah kelas yang memegang sebuah Instant
dan menggunakannya untuk sendiri ToString()
metode:
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();
}
}
Sekarang saya akhirnya melihat:
instant {NodaTime.Instant}
wrapper {1970-01-01T00:00:00Z}
Namun, atas saran Eren dalam komentar, jika saya berubah InstantWrapper
menjadi seorang struct, saya mendapatkan:
instant {NodaTime.Instant}
wrapper {InstantWrapper}
Sehingga dapat mengevaluasi Instant.ToString()
- selama itu dipanggil oleh ToString
metode lain ... yang ada di dalam kelas. Bagian kelas / struct tampaknya penting berdasarkan pada jenis variabel yang ditampilkan, bukan kode apa yang perlu dieksekusi untuk mendapatkan hasilnya.
Sebagai contoh lain dari ini, jika kita menggunakan:
object boxed = NodaConstants.UnixEpoch;
... lalu berfungsi dengan baik, menampilkan nilai yang tepat. Warna saya bingung.
sumber
DebuggerDisplayAttribute
akan menyebabkannya mencoba sedikit lebih keras.Jawaban:
Memperbarui:
Bug ini telah diperbaiki di Visual Studio 2015 Pembaruan 2. Beritahu saya jika Anda masih mengalami masalah mengevaluasi ToString pada nilai struct menggunakan Pembaruan 2 atau lebih baru.
Jawaban asli:
Anda mengalami keterbatasan bug / desain yang diketahui dengan Visual Studio 2015 dan memanggil ToString pada tipe struct. Ini juga bisa diamati ketika berhadapan dengan
System.DateTimeSpan
.System.DateTimeSpan.ToString()
bekerja di jendela evaluasi dengan Visual Studio 2013, tetapi tidak selalu berfungsi pada tahun 2015.Jika Anda tertarik dengan detail level rendah, inilah yang terjadi:
Untuk mengevaluasi
ToString
, debugger melakukan apa yang dikenal sebagai "evaluasi fungsi". Dalam istilah yang sangat disederhanakan, debugger menangguhkan semua utas dalam proses kecuali utas saat ini, mengubah konteks utas saat ini keToString
fungsi, menetapkan breakpoint pelindung tersembunyi, kemudian memungkinkan proses untuk melanjutkan. Ketika guard breakpoint terkena, debugger mengembalikan proses ke keadaan sebelumnya dan nilai kembali fungsi digunakan untuk mengisi jendela.Untuk mendukung ekspresi lambda, kami harus menulis ulang sepenuhnya CLR Expression Evaluator di Visual Studio 2015. Pada level tinggi, implementasinya adalah:
Karena eksekusi IL, debugger selalu berurusan dengan campuran rumit dari nilai "nyata" dan "palsu". Nilai nyata sebenarnya ada dalam proses yang sedang di-debug. Nilai-nilai palsu hanya ada dalam proses debugger. Untuk menerapkan semantik struct yang tepat, debugger selalu perlu membuat salinan nilai ketika mendorong nilai struct ke tumpukan IL. Nilai yang disalin tidak lagi menjadi nilai "nyata" dan sekarang hanya ada dalam proses debugger. Itu berarti jika nanti kita perlu melakukan evaluasi fungsi
ToString
, kita tidak bisa karena nilainya tidak ada dalam proses. Untuk mencoba dan mendapatkan nilai, kita perlu meniru pelaksanaanToString
metode. Meskipun kita dapat meniru beberapa hal, ada banyak keterbatasan. Misalnya, kami tidak dapat meniru kode asli dan kami tidak dapat melakukan panggilan ke nilai delegasi "nyata" atau panggilan pada nilai refleksi.Dengan semua itu dalam pikiran, di sini adalah apa yang menyebabkan berbagai perilaku yang Anda lihat:
NodaTime.Instant.ToString
-> Ini karena ini adalah tipe struct dan implementasi ToString tidak dapat ditiru oleh debugger seperti dijelaskan di atas.Thread.Sleep
tampaknya tidak membutuhkan waktu ketika dipanggil olehToString
struct -> Ini karena emulator mengeksekusiToString
. Thread.Sleep adalah metode asli, tetapi emulator menyadarinya dan mengabaikan panggilan. Kami melakukan ini untuk mencoba dan mendapatkan nilai untuk ditampilkan kepada pengguna. Penundaan tidak akan membantu dalam kasus ini.DisplayAttibute("ToString()")
bekerja. -> Itu membingungkan. Satu-satunya perbedaan antara panggilan implisit dariToString
danDebuggerDisplay
adalah bahwa setiap time-out dariToString
evaluasi implisit akan menonaktifkan semuaToString
evaluasi implisit untuk tipe itu sampai sesi debug berikutnya. Anda mungkin mengamati perilaku itu.Dalam hal masalah desain / bug, ini adalah sesuatu yang kami rencanakan untuk ditangani dalam rilis Visual Studio mendatang.
Semoga itu beres. Beritahu saya jika Anda memiliki pertanyaan lebih lanjut. :-)
sumber
innerResult
dimulai sebagai nol, loop tidak akan pernah berhenti dan akhirnya evaluasi akan habis. Bahkan, evaluasi hanya memungkinkan satu utas dalam proses dijalankan secara default, jadi Anda akan melihat perilaku yang sama terlepas dari apakah emulator digunakan atau tidak.