He escrito un visualizador de depuración de Visual Studio que apunta a DateTime ( repo ). Mi problema es que el lado del depurador solo pasa el valor de destino al lado depurado si la expresión de destino es de object , no de DateTime ( problema ).
He publicado un repositorio de GH que contiene un MCVE que reproduce el problema . El lado del depurador se ve así:
protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider) { var response = objectProvider.TransferObject(5); var msg = response switch { string s => s, IEnumerable e => string.Join(", ", e.Cast<object>()), _ => "Unhandled type" }; MessageBox.Show(msg); }y el lado depurado se ve así:
public override void TransferData(object target, Stream incomingData, Stream outgoingData) { int? repetitions = Deserialize(incomingData) switch { int i when i > 0 => i, string s when int.TryParse(s, out int i) && i > 0 => i, _ => null }; object toSerialize = repetitions is null ? $"Invalid value for repetitions" : target switch { DateTime dt => Repeat(dt, repetitions.Value).ToArray(), null => $"{nameof(target)} is null", _ => $"Not implemented for target of type {target.GetType().FullName}" as object }; Serialize(outgoingData, toSerialize); }Después de compilar e instalar el visualizador, y comenzar a depurar el siguiente código:
var dte = DateTime.UtcNow; object o = dte; si paso el cursor sobre o y activo el visualizador, el DateTime de destino se pasa al lado depurado y devuelve una matriz de DateTime . Pero si activo el visualizador en dte , obtengo que el target is null , lo que implica que el lado de depuración ha recibido null en el parámetro de target .
¿Qué podría estar causando esto? ¿Cómo podría solucionarlo?
TransferData ; la anulación de GetData siempre obtiene el valor objetivo (en realidad estoy usando GetData para solucionar esto, pero realmente prefiero usar GetData para otra cosa). Intenté probar ReplaceData / ReplaceObject , pero la propiedad IsObjectReplaceable siempre devuelve false .TimeSpan , DateTimeOffset y una struct personalizada) y observé el mismo comportamiento. Sin embargo, cuando probé contra int , el proceso de destino falla y la sesión de depuración se interrumpe.DateTime? muestra el mismo comportamiento que DateTime ; Me imagino que esto se debe a que ambos están serializados de la misma manera.int En respuesta a este comentario , el mensaje de error al visualizar un int es el siguiente:
El proceso de destino finalizó con el código -1073740791 (0xC0000409) mientras evaluaba la función 'Microsoft.VisualStudio.DebuggerVisualizers.DebugeeSide.Impl.ClrCustomVisualizerDebuggeeHost.TransferData'.
Si el problema ocurre regularmente, considere deshabilitar la configuración de Herramientas->Opciones "Depuración->General->Habilitar evaluación de propiedades y otras llamadas a funciones implícitas" o depurar la causa evaluando la expresión desde la ventana Inmediato. Consulte la ayuda para obtener información sobre cómo hacer esto.
seguido de otro mensaje:
No se pudo cargar el visor personalizado.
sobre el cual el proceso de destino se bloquea y finaliza la sesión de depuración.
Intenté sin éxito adjuntar un depurador usando un punto de interrupción de código ( Debugger.Break() ). Si devuelvo la pila de llamadas del visualizador ( new System.Diagnostics.StackTrace().ToString() ) y el visualizador se ejecuta correctamente, obtengo lo siguiente:
en SimpleValueTypeVisualizer.Debuggee.VisualizerObjectSource.TransferData(Objeto objetivo, Stream incomingData, Stream outgoingData)
en Microsoft.VisualStudio.DebuggerVisualizers.DebuggeeSide.Impl.ClrCustomVisualizerDebuggeeHost.TransferData(Object visualizatedObject, Byte[] uiSideData)
en TestNoRef.Program.Main(String[] argumentos)
lo que parecería implicar alguna excepción en Microsoft.VisualStudio.DebuggerVisualizers.DebuggeeSide.Impl.ClrCustomVisualizerDebuggeeHost.TransferData .
Cuando abrí DebuggerVisualizers.dll usando ILSpy, el método TransferData relevante se ve así:
// Microsoft.VisualStudio.DebuggerVisualizers.DebuggeeSide.Impl.ClrCustomVisualizerDebuggeeHost using System.IO; public byte[] TransferData(object visualizedObject, byte[] uiSideData) { MemoryStream memoryStream = new MemoryStream(); MemoryStream incomingData = ((uiSideData != null) ? new MemoryStream(uiSideData) : null); m_debuggeeSideVisualizerObject.TransferData(visualizedObject, incomingData, memoryStream); return memoryStream.ToArray(); } Supongo que la excepción está en la tercera línea del método ( MemoryStream incomingData = ... ). Pero todavía no tengo claro los detalles de la excepción, particularmente por qué el problema solo surge con un valor sin caja y no con un valor con caja.
Según este comentario , incluyo datos del registro de eventos creado al abrir el visualizador en una expresión de tipo int :
Log Name: Application Source: Application Error Date: 22/04/2021 12:14:36 Event ID: 1000 Task Category: (100) Level: Error Keywords: Classic User: N/A Computer: LAPTOP-7O43T4OO Description: Faulting application name: TestNoRef.exe, version: 1.0.0.0, time stamp: 0xd9f9e12d Faulting module name: clr.dll, version: 4.8.4341.0, time stamp: 0x6023024f Exception code: 0xc0000409 Fault offset: 0x00574845 Faulting process ID: 0x94c4 Faulting application start time: 0x01d73757c33e87c0 Faulting application path: *********** Faulting module path: C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll Report ID: 1dcf070b-71ff-4279-be71-822698cc6168 Faulting package full name: Faulting package-relative application ID: Event Xml: <Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event"> <System> <Provider Name="Application Error" /> <EventID Qualifiers="0">1000</EventID> <Version>0</Version> <Level>2</Level> <Task>100</Task> <Opcode>0</Opcode> <Keywords>0x80000000000000</Keywords> <TimeCreated SystemTime="2021-04-22T09:14:36.4507272Z" /> <EventRecordID>1180760705</EventRecordID> <Correlation /> <Execution ProcessID="0" ThreadID="0" /> <Channel>Application</Channel> <Computer>LAPTOP-7O43T4OO</Computer> <Security /> </System> <EventData> <Data>TestNoRef.exe</Data> <Data>1.0.0.0</Data> <Data>d9f9e12d</Data> <Data>clr.dll</Data> <Data>4.8.4341.0</Data> <Data>6023024f</Data> <Data>c0000409</Data> <Data>00574845</Data> <Data>94c4</Data> <Data>01d73757c33e87c0</Data> <Data>***********</Data> <Data>C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll</Data> <Data>1dcf070b-71ff-4279-be71-822698cc6168</Data> <Data> </Data> <Data> </Data> </EventData> </Event>No puedo encontrar una solución adecuada. Podría ser solo un error introducido en una de las últimas versiones y nadie ha encontrado este problema con los tipos de valor hasta ahora. En realidad, lo intenté
DateTime dte = DateTime.UtcNow; ValueType vt = dte; Y nuevamente funciona con vt pero no con dte . Agregué un objetivo explícito a net48 por si acaso, pero no cambió nada.
Lo mejor que pude encontrar es una solución muy similar a la que supongo que Zev Spitz está usando, pero tratando de no desperdiciar la anulación de GetData solo para obtener el valor objetivo. No es una solución muy agradable, me temo.
Siempre que desee usar GetData para recuperar un valor diferente, pero se usará en su DialogDebuggerVisualizer.Show override, puede almacenar su valor dentro del objeto VisualizerObjectSource cuando llame a GetData y recuperarlo cuando llame a TransferData, sin transferirlo realmente del depurador al depurador.
public class VisualizerObjectSource : Microsoft.VisualStudio.DebuggerVisualizers.VisualizerObjectSource { /*static*/ DateTime? _lastDatetime=null; public override void TransferData(object target, Stream incomingData, Stream outgoingData) { target = _lastDatetime; //Calculate here the output value object toSerialize = " is null = " + (target==null).ToString(); Serialize(outgoingData, toSerialize); } public override void GetData(object target, Stream outgoingData) { _lastDatetime = (DateTime)target; //Calculate here what you want to be returned by GetData base.GetData(" The stuff you want to return ", outgoingData); } }y en su lado del Depurador, asegúrese de llamar a GetObject/GetData antes de llamar a TransferObject()
protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider) { object MyCustomStuff =objectProvider.GetObject(); var response = objectProvider.TransferObject(5); //[...] string msg = response .ToString(); MessageBox.Show(msg); }Como mencioné en los comentarios, en realidad no se requiere TransferData . Además, se puede evitar toda la serialización BinaryFormatter (desafortunadamente, solo cuando se transfieren los datos al visualizador del depurador y no cuando se reemplaza un valor editado, pero hay otro truco para eso).
En primer lugar, considere la siguiente configuración:
[assembly: DebuggerVisualizer(typeof(DateTimeVisualizer), typeof(DateTimeSerializer), Target = typeof(DateTime), Description = "DateTime Debugger Visualizer")]Donde el serializador es el siguiente:
// Note that TransferData is not overridden, and we do not call base.GetData so we can // avoid using BinaryFormatter (which often has issues when debugging a .NET Core or newer project) internal class DateTimeSerializer : VisualizerObjectSource { public override void GetData(object target, Stream outgoingData) { var dateTime = (DateTime)target; // Note: do not dispose the writer so the outgoingData remains open. // If targeting newer frameworks you can use the leaveOpen parameter, too. var writer = new BinaryWriter(outgoingData); // What a tiny payload compared to the default BinaryFormatter result... writer.Write(dateTime.Ticks); writer.Write((int)dateTime.Kind); } } Y en el visualizador mismo, es importante no llamar a GetObject , que intentaría deserializar su flujo mediante BinaryFormatter . En su lugar, use GetData , que devuelve la secuencia sin procesar:
internal class DateTimeDebuggerVisualizer : DialogDebuggerVisualizer { protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider) { // GetObject would fail here as we have a custom written stream var reader = new BinaryReader(objectProvider.GetData()); var dateTime = new DateTime(reader.ReadInt64(), (DateTimeKind)reader.ReadInt32()); // Show the debugger [...] } Si su depurador puede editar los datos, entonces reemplazar el valor es otro problema. Desafortunadamente, la serialización de BinaryFormatter no se puede evitar en ese caso. Puede ser un problema si su objeto no es serializable en todos los objetivos (por ejemplo, en .NET Core y superior).
Por supuesto, esto no es un problema en el caso de DateTime , así que solo como una nota al margen: en tal caso, el truco puede ser colocar datos serializables personalizados en ReplaceObject que implementa IObjectReference para que pueda devolver el resultado personalizado en IObjectReference.GetRealObject . Aquí hay uno de esos ejemplos de mis serializadores.