Отладка C#: выбрать подходящий инструмент
Запускаемый пример и границы Output, Remote Inspector, Profiler и управляемого отладчика.
Начните с симптома
| Симптом | Инструмент и результат |
|---|---|
| Проект не собирается | Вывод dotnet build; исправьте первую ошибку |
| Неправильный результат команды | Output, входы и результат операции |
| Не тот объект или значение свойства | Удалённое дерево и Inspector работающего приложения |
| Долгий кадр | Метрики кадра; затем измерение конкретной нагрузки |
| Рост памяти | Повторяемый сценарий и отдельный учёт нативных объектов/C#-памяти |
| Нужны C# locals или пошаговое выполнение | Отдельно настроенный управляемый отладчик |
В Reckfell 1.0.0 передача ошибок C# и свойств объекта через удалённую диагностику работает. Это не равнозначно полной поддержке управляемых breakpoint, locals и watches во встроенной панели. Встроенный профиль методов C# здесь также не предоставляется.
Запустить проверочный уровень
Нужен C#-проект с Reckfell.NET.Sdk 1.0.0 и net10.0.
- Сохраните все четыре файла ниже в проект с точными именами.
- Выполните dotnet build.
- Создайте уровень с корневым Entity, прикрепите DiagnosticsExample.cs и сохраните как diagnostics.rscene.
- Запустите уровень. В Output ожидается DIAGNOSTICS checksum=8386560; lifetime=True; ok=True.
Строки MEASURE и MEMORY зависят от машины и запуска. Их числа не являются нормативом производительности. В работающем дереве найдите DiagnosticsExample/InspectionTarget: DiagnosticToken равен 424242. При запуске отдельного уровня имя корня может отличаться; ребёнок называется InspectionTarget.
DebugInspectionTarget.cs
using Reckfell;
public partial class DebugInspectionTarget : Entity
{
[Export] public int DiagnosticToken { get; set; } = 424242;
}WorkMeasurement.cs
using System;
using System.Diagnostics;
public readonly record struct WorkMeasurement(double Milliseconds, long AllocatedBytes, long Checksum)
{
public static WorkMeasurement Run(int iterations)
{
if (iterations < 0 || iterations > 10_000_000)
throw new ArgumentOutOfRangeException(nameof(iterations));
long before = GC.GetAllocatedBytesForCurrentThread();
long start = Stopwatch.GetTimestamp();
long checksum = 0;
for (int i = 0; i < iterations; i++) checksum += i;
double elapsed = Stopwatch.GetElapsedTime(start).TotalMilliseconds;
long allocated = GC.GetAllocatedBytesForCurrentThread() - before;
return new WorkMeasurement(elapsed, allocated, checksum);
}
}ObjectLifetimeProbe.cs
using System;
using System.Collections.Generic;
using Reckfell;
public static class ObjectLifetimeProbe
{
public static bool Run()
{
var retained = new List<Entity>();
var parent = new Entity();
var child = new Entity();
retained.Add(child);
parent.AddChild(child);
parent.RemoveChild(child);
parent.Free();
bool detachedSurvived = ReckfellObject.IsInstanceValid(child);
child.Free();
bool wrapperOutlivedNative = retained[0] is not null &&
!ReckfellObject.IsInstanceValid(retained[0]);
retained.Clear();
return detachedSurvived && wrapperOutlivedNative;
}
public static void PrintSnapshot()
{
double objects = Performance.GetMonitor(Performance.Monitor.ObjectCount);
double entities = Performance.GetMonitor(Performance.Monitor.ObjectNodeCount);
double orphans = Performance.GetMonitor(Performance.Monitor.ObjectOrphanNodeCount);
long managed = GC.GetTotalMemory(false);
ReckfellApi.Print($"MEMORY objects={objects}; entitiesInTree={entities}; outsideTree={orphans}; managedApproxBytes={managed}");
}
}DiagnosticsExample.cs
using System.Globalization;
using Reckfell;
public partial class DiagnosticsExample : Entity
{
public bool Succeeded { get; private set; }
public override void _Ready()
{
var target = new DebugInspectionTarget { Name = "InspectionTarget" };
AddChild(target);
_ = WorkMeasurement.Run(64);
WorkMeasurement measured = WorkMeasurement.Run(4096);
bool lifetime = ObjectLifetimeProbe.Run();
Succeeded = measured.Checksum == 8386560 && lifetime;
ReckfellApi.Print($"DIAGNOSTICS checksum={measured.Checksum}; lifetime={lifetime}; ok={Succeeded}");
ReckfellApi.Print("MEASURE ms=" + measured.Milliseconds.ToString("F4", CultureInfo.InvariantCulture) +
"; currentThreadAllocatedBytes=" + measured.AllocatedBytes);
ObjectLifetimeProbe.PrintSnapshot();
}
}Сохранить воспроизводимость
Запишите версию движка, сборку Debug/ExportRelease, платформу, шаги до сбоя и первую ошибку. Меняйте одну причину за раз. Для журнала используйте настройки логирования, а не снимок последней строки без контекста.