xaf-memory-leaks

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

XAF: Memory Leak Prevention

XAF:内存泄漏预防

Root Causes

根本原因

CauseSymptom
Event handler not unsubscribedMemory grows with navigation; handler fires multiple times
ObjectSpace not disposedMemory grows proportional to data accessed
Static reference to instanceObjects never GC'd; growing memory profile
CollectionSource not disposedCached data retained after view closes
ObjectSpace/objects in SessionMemory scales with active user count; session timeout failures
Controller holds undisposed resourcesFinalizer queue pressure; slow GC

原因症状
事件处理器未取消订阅内存随导航操作持续增长;处理器被多次触发
ObjectSpace未释放内存增长量与访问的数据量成正比
实例被静态引用对象永远不会被垃圾回收;内存占用持续攀升
CollectionSource未释放视图关闭后缓存数据仍被保留
Session中存在ObjectSpace/对象内存占用随活跃用户数增长;会话超时失败
控制器持有未释放的资源终结器队列压力过大;垃圾回收速度变慢

Event Handler Pattern — Most Common Leak

事件处理器模式——最常见的泄漏原因

Every
+=
in
OnActivated
needs a matching
-=
in both
OnDeactivated
and
Dispose
.
csharp
public class SafeController : ViewController {
    private bool eventsSubscribed;

    protected override void OnActivated() {
        base.OnActivated();
        if (!eventsSubscribed) {
            View.SelectionChanged += View_SelectionChanged;
            View.CurrentObjectChanged += View_CurrentObjectChanged;
            View.ObjectSpace.ObjectChanged += ObjectSpace_ObjectChanged;
            eventsSubscribed = true;
        }
    }

    protected override void OnDeactivated() {
        UnsubscribeEvents();
        base.OnDeactivated();
    }

    protected override void Dispose(bool disposing) {
        if (disposing) UnsubscribeEvents(); // safety net
        base.Dispose(disposing);
    }

    private void UnsubscribeEvents() {
        if (!eventsSubscribed) return;
        if (View != null) {
            View.SelectionChanged -= View_SelectionChanged;
            View.CurrentObjectChanged -= View_CurrentObjectChanged;
        }
        if (View?.ObjectSpace != null)
            View.ObjectSpace.ObjectChanged -= ObjectSpace_ObjectChanged;
        eventsSubscribed = false;
    }
}

所有在
OnActivated
中通过
+=
订阅的事件,都需要在
OnDeactivated
Dispose
中都有对应的
-=
取消订阅操作。
csharp
public class SafeController : ViewController {
    private bool eventsSubscribed;

    protected override void OnActivated() {
        base.OnActivated();
        if (!eventsSubscribed) {
            View.SelectionChanged += View_SelectionChanged;
            View.CurrentObjectChanged += View_CurrentObjectChanged;
            View.ObjectSpace.ObjectChanged += ObjectSpace_ObjectChanged;
            eventsSubscribed = true;
        }
    }

    protected override void OnDeactivated() {
        UnsubscribeEvents();
        base.OnDeactivated();
    }

    protected override void Dispose(bool disposing) {
        if (disposing) UnsubscribeEvents(); // 安全兜底
        base.Dispose(disposing);
    }

    private void UnsubscribeEvents() {
        if (!eventsSubscribed) return;
        if (View != null) {
            View.SelectionChanged -= View_SelectionChanged;
            View.CurrentObjectChanged -= View_CurrentObjectChanged;
        }
        if (View?.ObjectSpace != null)
            View.ObjectSpace.ObjectChanged -= ObjectSpace_ObjectChanged;
        eventsSubscribed = false;
    }
}

Comprehensive Disposal Pattern — ResourceTrackingController

全量释放模式——ResourceTrackingController

Use
List<IDisposable>
+
List<Action>
to track all resources safely:
csharp
public class ResourceTrackingController : ViewController {
    private readonly List<IDisposable> _disposables = new();
    private readonly List<Action> _cleanups = new();
    private bool _disposed;

    protected T Track<T>(T resource) where T : IDisposable {
        _disposables.Add(resource);
        return resource;
    }

    protected void AddCleanup(Action cleanup) => _cleanups.Add(cleanup);

    protected override void OnActivated() {
        base.OnActivated();
        // Subscribe and auto-track cleanup
        View.SelectionChanged += OnSelectionChanged;
        AddCleanup(() => { if (View != null) View.SelectionChanged -= OnSelectionChanged; });

        // Track a CollectionSource
        var cs = Track(new CollectionSource(ObjectSpace, typeof(MyObject)));
    }

    protected override void OnDeactivated() {
        RunCleanups();
        base.OnDeactivated();
    }

    protected override void Dispose(bool disposing) {
        if (!_disposed && disposing) {
            RunCleanups();
            foreach (var d in _disposables)
                try { d?.Dispose(); } catch { }
            _disposables.Clear();
            _disposed = true;
        }
        base.Dispose(disposing);
    }

    private void RunCleanups() {
        foreach (var action in _cleanups)
            try { action(); } catch { }
        _cleanups.Clear();
    }
}

使用
List<IDisposable>
+
List<Action>
安全追踪所有资源:
csharp
public class ResourceTrackingController : ViewController {
    private readonly List<IDisposable> _disposables = new();
    private readonly List<Action> _cleanups = new();
    private bool _disposed;

    protected T Track<T>(T resource) where T : IDisposable {
        _disposables.Add(resource);
        return resource;
    }

    protected void AddCleanup(Action cleanup) => _cleanups.Add(cleanup);

    protected override void OnActivated() {
        base.OnActivated();
        // 订阅事件并自动追踪清理逻辑
        View.SelectionChanged += OnSelectionChanged;
        AddCleanup(() => { if (View != null) View.SelectionChanged -= OnSelectionChanged; });

        // 追踪CollectionSource
        var cs = Track(new CollectionSource(ObjectSpace, typeof(MyObject)));
    }

    protected override void OnDeactivated() {
        RunCleanups();
        base.OnDeactivated();
    }

    protected override void Dispose(bool disposing) {
        if (!_disposed && disposing) {
            RunCleanups();
            foreach (var d in _disposables)
                try { d?.Dispose(); } catch { }
            _disposables.Clear();
            _disposed = true;
        }
        base.Dispose(disposing);
    }

    private void RunCleanups() {
        foreach (var action in _cleanups)
            try { action(); } catch { }
        _cleanups.Clear();
    }
}

Weak Event Subscription

弱事件订阅

For long-lived controllers that subscribe to events on short-lived objects:
csharp
public class WeakEventSubscription : IDisposable {
    private WeakReference _sourceRef;
    private readonly string _eventName;
    private readonly EventHandler _handler;

    public WeakEventSubscription(object source, string eventName, EventHandler handler) {
        _sourceRef = new WeakReference(source);
        _eventName = eventName;
        _handler = handler;
        source.GetType().GetEvent(eventName)?.AddEventHandler(source, handler);
    }

    public void Dispose() {
        var source = _sourceRef?.Target;
        if (source != null)
            source.GetType().GetEvent(_eventName)?.RemoveEventHandler(source, _handler);
        _sourceRef = null;
    }
}

// Usage in controller:
private readonly List<WeakEventSubscription> _weakSubs = new();

protected override void OnActivated() {
    base.OnActivated();
    _weakSubs.Add(new WeakEventSubscription(
        View, nameof(View.SelectionChanged), OnSelectionChanged));
}

protected override void Dispose(bool disposing) {
    if (disposing) {
        foreach (var sub in _weakSubs) sub.Dispose();
        _weakSubs.Clear();
    }
    base.Dispose(disposing);
}

适用于需要订阅短生命周期对象事件的长生命周期控制器:
csharp
public class WeakEventSubscription : IDisposable {
    private WeakReference _sourceRef;
    private readonly string _eventName;
    private readonly EventHandler _handler;

    public WeakEventSubscription(object source, string eventName, EventHandler handler) {
        _sourceRef = new WeakReference(source);
        _eventName = eventName;
        _handler = handler;
        source.GetType().GetEvent(eventName)?.AddEventHandler(source, handler);
    }

    public void Dispose() {
        var source = _sourceRef?.Target;
        if (source != null)
            source.GetType().GetEvent(_eventName)?.RemoveEventHandler(source, _handler);
        _sourceRef = null;
    }
}

// 控制器中的使用方式:
private readonly List<WeakEventSubscription> _weakSubs = new();

protected override void OnActivated() {
    base.OnActivated();
    _weakSubs.Add(new WeakEventSubscription(
        View, nameof(View.SelectionChanged), OnSelectionChanged));
}

protected override void Dispose(bool disposing) {
    if (disposing) {
        foreach (var sub in _weakSubs) sub.Dispose();
        _weakSubs.Clear();
    }
    base.Dispose(disposing);
}

ObjectSpace — Scoped Disposal

ObjectSpace——作用域释放

Never store
IObjectSpace
in a static field, singleton, or Session.
csharp
// ✅ Short-lived operation
public void ProcessData() {
    using var os = Application.CreateObjectSpace(typeof(MyObject));
    var obj = os.FindObject<MyObject>(CriteriaOperator.Parse("Name = ?", "Test"));
    obj.Status = Status.Processed;
    os.CommitChanges();
} // disposed here — tracked objects released

// ❌ Anti-patterns
private static IObjectSpace _globalOs;           // never
Session["XafObjectSpace"] = Application.CreateObjectSpace(); // never in Session

绝对不要
IObjectSpace
存储在静态字段、单例或者Session中。
csharp
// ✅ 短生命周期操作
public void ProcessData() {
    using var os = Application.CreateObjectSpace(typeof(MyObject));
    var obj = os.FindObject<MyObject>(CriteriaOperator.Parse("Name = ?", "Test"));
    obj.Status = Status.Processed;
    os.CommitChanges();
} // 在此处释放——被追踪的对象会被回收

// ❌ 反模式
private static IObjectSpace _globalOs;           // 绝对禁止
Session["XafObjectSpace"] = Application.CreateObjectSpace(); // 绝对禁止存储在Session中

ObjectSpace — Batch Processing Large Datasets

ObjectSpace——大数据集批量处理

csharp
public void ProcessAllRecords() {
    const int batchSize = 500;
    int skip = 0;

    while (true) {
        using var os = Application.CreateObjectSpace(typeof(MyObject));
        var batch = os.GetObjects<MyObject>()
            .Skip(skip).Take(batchSize).ToList();

        if (batch.Count == 0) break;

        foreach (var obj in batch)
            Process(obj);

        os.CommitChanges();
        skip += batch.Count;

        // Force GC between large batches
        if (skip % 5000 == 0) {
            GC.Collect();
            GC.WaitForPendingFinalizers();
        }
    }
}

csharp
public void ProcessAllRecords() {
    const int batchSize = 500;
    int skip = 0;

    while (true) {
        using var os = Application.CreateObjectSpace(typeof(MyObject));
        var batch = os.GetObjects<MyObject>()
            .Skip(skip).Take(batchSize).ToList();

        if (batch.Count == 0) break;

        foreach (var obj in batch)
            Process(obj);

        os.CommitChanges();
        skip += batch.Count;

        // 处理大批数据之间强制触发GC
        if (skip % 5000 == 0) {
            GC.Collect();
            GC.WaitForPendingFinalizers();
        }
    }
}

ObjectSpace Pool (Advanced — High-Throughput Scenarios)

ObjectSpace池(高级——高吞吐量场景)

csharp
public class ObjectSpacePool : IDisposable {
    private readonly ConcurrentQueue<IObjectSpace> _pool = new();
    private readonly XafApplication _app;
    private readonly int _maxSize;
    private int _currentSize;

    public ObjectSpacePool(XafApplication app, int maxSize = 10) {
        _app = app; _maxSize = maxSize;
    }

    public IObjectSpace Rent() =>
        _pool.TryDequeue(out var os)
            ? (Interlocked.Decrement(ref _currentSize), os).os
            : _app.CreateObjectSpace();

    public void Return(IObjectSpace os) {
        if (os == null) return;
        if (os.IsModified) os.ReloadChangedObjects();
        if (_currentSize < _maxSize) {
            _pool.Enqueue(os);
            Interlocked.Increment(ref _currentSize);
        } else os.Dispose();
    }

    public void Dispose() {
        while (_pool.TryDequeue(out var os)) os.Dispose();
    }
}

csharp
public class ObjectSpacePool : IDisposable {
    private readonly ConcurrentQueue<IObjectSpace> _pool = new();
    private readonly XafApplication _app;
    private readonly int _maxSize;
    private int _currentSize;

    public ObjectSpacePool(XafApplication app, int maxSize = 10) {
        _app = app; _maxSize = maxSize;
    }

    public IObjectSpace Rent() =>
        _pool.TryDequeue(out var os)
            ? (Interlocked.Decrement(ref _currentSize), os).os
            : _app.CreateObjectSpace();

    public void Return(IObjectSpace os) {
        if (os == null) return;
        if (os.IsModified) os.ReloadChangedObjects();
        if (_currentSize < _maxSize) {
            _pool.Enqueue(os);
            Interlocked.Increment(ref _currentSize);
        } else os.Dispose();
    }

    public void Dispose() {
        while (_pool.TryDequeue(out var os)) os.Dispose();
    }
}

CollectionSource Disposal

CollectionSource释放

csharp
public class ListController : ViewController<ListView> {
    private CollectionSource _cs;

    protected override void OnActivated() {
        base.OnActivated();
        _cs = new CollectionSource(ObjectSpace, typeof(MyObject));
        _cs.Criteria["Date"] = CriteriaOperator.Parse(
            "CreatedOn >= ?", DateTime.Today.AddDays(-30));
        View.CollectionSource = _cs;
    }

    protected override void OnDeactivated() {
        _cs?.Dispose(); _cs = null;
        base.OnDeactivated();
    }

    protected override void Dispose(bool disposing) {
        if (disposing) { _cs?.Dispose(); _cs = null; }
        base.Dispose(disposing);
    }
}

csharp
public class ListController : ViewController<ListView> {
    private CollectionSource _cs;

    protected override void OnActivated() {
        base.OnActivated();
        _cs = new CollectionSource(ObjectSpace, typeof(MyObject));
        _cs.Criteria["Date"] = CriteriaOperator.Parse(
            "CreatedOn >= ?", DateTime.Today.AddDays(-30));
        View.CollectionSource = _cs;
    }

    protected override void OnDeactivated() {
        _cs?.Dispose(); _cs = null;
        base.OnDeactivated();
    }

    protected override void Dispose(bool disposing) {
        if (disposing) { _cs?.Dispose(); _cs = null; }
        base.Dispose(disposing);
    }
}

Session / HttpContext (WebForms Only)

Session / HttpContext(仅WebForms适用)

Anti-Patterns

反模式

csharp
// ❌ ObjectSpace in Session — lives until session expires (30 min+ of retained objects)
Session["XafObjectSpace"] = Application.CreateObjectSpace();

// ❌ Large collection in Session
Session["AllCustomers"] = objectSpace.GetObjects<Customer>();

// ❌ No cleanup on session end
protected void Application_Session_End(object sender, EventArgs e) {
    // Missing: dispose XAF objects
}
csharp
// ❌ ObjectSpace存储在Session中——会存活到会话过期(对象会被保留30分钟以上)
Session["XafObjectSpace"] = Application.CreateObjectSpace();

// ❌ 大型集合存储在Session中
Session["AllCustomers"] = objectSpace.GetObjects<Customer>();

// ❌ 会话结束时没有清理逻辑
protected void Application_Session_End(object sender, EventArgs e) {
    // 缺失:释放XAF对象的逻辑
}

Correct Patterns

正确模式

csharp
// ✅ Request-scoped ObjectSpace (disposed at end of request)
public static IObjectSpace GetRequestObjectSpace(XafApplication app) {
    var ctx = HttpContext.Current;
    if (ctx == null) return app.CreateObjectSpace();

    var os = ctx.Items["XafOs"] as IObjectSpace;
    if (os == null) {
        os = app.CreateObjectSpace();
        ctx.Items["XafOs"] = os;
        ctx.DisposeOnPipelineCompleted(os); // auto-dispose at request end
    }
    return os;
}

// ✅ Session_End cleanup — dispose all IDisposable objects stored in session
public class SessionCleanupModule : IHttpModule {
    public void Init(HttpApplication context) {
        context.Session_End += (s, e) => {
            var session = HttpContext.Current?.Session;
            if (session == null) return;
            foreach (string key in session.Keys.Cast<string>().ToList()) {
                if (session[key] is IDisposable d) {
                    try { d.Dispose(); session.Remove(key); }
                    catch (Exception ex) { Tracing.Tracer.LogError(ex.ToString()); }
                }
            }
        };
    }
    public void Dispose() { }
}

csharp
// ✅ 请求级作用域ObjectSpace(请求结束时自动释放)
public static IObjectSpace GetRequestObjectSpace(XafApplication app) {
    var ctx = HttpContext.Current;
    if (ctx == null) return app.CreateObjectSpace();

    var os = ctx.Items["XafOs"] as IObjectSpace;
    if (os == null) {
        os = app.CreateObjectSpace();
        ctx.Items["XafOs"] = os;
        ctx.DisposeOnPipelineCompleted(os); // 请求结束时自动释放
    }
    return os;
}

// ✅ Session_End清理逻辑——释放Session中存储的所有IDisposable对象
public class SessionCleanupModule : IHttpModule {
    public void Init(HttpApplication context) {
        context.Session_End += (s, e) => {
            var session = HttpContext.Current?.Session;
            if (session == null) return;
            foreach (string key in session.Keys.Cast<string>().ToList()) {
                if (session[key] is IDisposable d) {
                    try { d.Dispose(); session.Remove(key); }
                    catch (Exception ex) { Tracing.Tracer.LogError(ex.ToString()); }
                }
            }
        };
    }
    public void Dispose() { }
}

Controller Lifecycle Tracker (Diagnostic)

控制器生命周期追踪器(诊断用)

Detect duplicate activations or missing deactivation in development:
csharp
public class ControllerLifecycleTracker {
    private static readonly Dictionary<string, int> _active = new();

    public static void TrackActivation(Controller controller) {
        var key = controller.GetType().Name;
        _active[key] = _active.GetValueOrDefault(key, 0) + 1;
        if (_active[key] > 1)
            Tracing.Tracer.LogWarning(
                $"Multiple active instances of {key}: {_active[key]}");
    }

    public static void TrackDeactivation(Controller controller) {
        var key = controller.GetType().Name;
        if (_active.ContainsKey(key))
            if (--_active[key] <= 0) _active.Remove(key);
    }
}

// In your controller (development only):
protected override void OnActivated() {
    base.OnActivated();
    ControllerLifecycleTracker.TrackActivation(this);
}
protected override void OnDeactivated() {
    ControllerLifecycleTracker.TrackDeactivation(this);
    base.OnDeactivated();
}

在开发环境检测重复激活或缺失取消激活的问题:
csharp
public class ControllerLifecycleTracker {
    private static readonly Dictionary<string, int> _active = new();

    public static void TrackActivation(Controller controller) {
        var key = controller.GetType().Name;
        _active[key] = _active.GetValueOrDefault(key, 0) + 1;
        if (_active[key] > 1)
            Tracing.Tracer.LogWarning(
                $"Multiple active instances of {key}: {_active[key]}");
    }

    public static void TrackDeactivation(Controller controller) {
        var key = controller.GetType().Name;
        if (_active.ContainsKey(key))
            if (--_active[key] <= 0) _active.Remove(key);
    }
}

// 在你的控制器中使用(仅开发环境):
protected override void OnActivated() {
    base.OnActivated();
    ControllerLifecycleTracker.TrackActivation(this);
}
protected override void OnDeactivated() {
    ControllerLifecycleTracker.TrackDeactivation(this);
    base.OnDeactivated();
}

Navigation Memory Monitor (Diagnostic)

导航内存监控器(诊断用)

csharp
public class NavigationMonitorController : WindowController {
    private static int _navCount;
    private static readonly Dictionary<string, int> _viewCounts = new();

    protected override void OnFrameAssigned() {
        base.OnFrameAssigned();
        if (Frame != null) Frame.ViewChanged += Frame_ViewChanged;
    }

    private void Frame_ViewChanged(object sender, ViewChangedEventArgs e) {
        _navCount++;
        if (e.View != null) {
            var t = e.View.GetType().Name;
            _viewCounts[t] = _viewCounts.GetValueOrDefault(t, 0) + 1;
        }

        if (_navCount % 10 == 0) {
            GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect();
            Tracing.Tracer.LogValue("Navigations", _navCount);
            Tracing.Tracer.LogValue("Memory after nav",
                GC.GetTotalMemory(true));
        }
    }

    protected override void Dispose(bool disposing) {
        if (disposing && Frame != null)
            Frame.ViewChanged -= Frame_ViewChanged;
        base.Dispose(disposing);
    }
}

csharp
public class NavigationMonitorController : WindowController {
    private static int _navCount;
    private static readonly Dictionary<string, int> _viewCounts = new();

    protected override void OnFrameAssigned() {
        base.OnFrameAssigned();
        if (Frame != null) Frame.ViewChanged += Frame_ViewChanged;
    }

    private void Frame_ViewChanged(object sender, ViewChangedEventArgs e) {
        _navCount++;
        if (e.View != null) {
            var t = e.View.GetType().Name;
            _viewCounts[t] = _viewCounts.GetValueOrDefault(t, 0) + 1;
        }

        if (_navCount % 10 == 0) {
            GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect();
            Tracing.Tracer.LogValue("Navigations", _navCount);
            Tracing.Tracer.LogValue("Memory after nav",
                GC.GetTotalMemory(true));
        }
    }

    protected override void Dispose(bool disposing) {
        if (disposing && Frame != null)
            Frame.ViewChanged -= Frame_ViewChanged;
        base.Dispose(disposing);
    }
}

Static Reference Anti-Patterns

静态引用反模式

csharp
// ❌ Never store controller/view/ObjectSpace in static fields
private static List<Controller> _allControllers = new();  // leak
private static IObjectSpace _sessionOs;                    // leak
private static MyController _instance;                     // leak

// ✅ Use instance fields; clean up in Dispose
private readonly List<IDisposable> _ownedResources = new();

csharp
// ❌ 绝对不要将控制器/视图/ObjectSpace存储在静态字段中
private static List<Controller> _allControllers = new();  // 泄漏
private static IObjectSpace _sessionOs;                    // 泄漏
private static MyController _instance;                     // 泄漏

// ✅ 使用实例字段;在Dispose中清理
private readonly List<IDisposable> _ownedResources = new();

Warning Signs

预警信号

SignLikely Cause
Memory grows with each navigationEvent handlers not unsubscribed in OnDeactivated
Same handler fires multiple timesController activated multiple times without deactivation
Memory scales with user countObjectSpace or objects stored in Session
OutOfMemoryException
under normal load
ObjectSpace retained / large unconstrained query
GC pauses increase over timeStatic collections or long-lived ObjectSpaces
IIS app pool recyclingSession memory pressure
Session timeout failuresLarge objects (ObjectSpace) in session

信号可能原因
每次导航后内存都会增长OnDeactivated中未取消订阅事件处理器
同一个处理器被多次触发控制器被多次激活但没有对应取消激活操作
内存占用随用户数增长ObjectSpace或对象被存储在Session中
正常负载下出现
OutOfMemoryException
ObjectSpace被保留/存在无约束的大查询
GC停顿时间随时间变长静态集合或长生命周期的ObjectSpace
IIS应用池频繁回收Session内存压力过大
会话超时失败Session中存在大型对象(如ObjectSpace)

Diagnostic Tools

诊断工具

csharp
// Enable XAF tracing to monitor ObjectSpace create/dispose
Tracing.Tracer.Initialize(TraceLevel.Verbose,
    Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\xaf_trace.log");

// Memory snapshot (development only)
var before = GC.GetTotalMemory(false);
GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect();
Tracing.Tracer.LogValue("Retained after GC", GC.GetTotalMemory(true));

// ObjectSpace internal tracking (debug reflection)
public static void LogObjectSpaceStats(IObjectSpace os) {
    if (os is not BaseObjectSpace baseOs) return;
    var modified = typeof(BaseObjectSpace)
        .GetField("modifiedObjects", BindingFlags.NonPublic | BindingFlags.Instance)
        ?.GetValue(baseOs) as IDictionary;
    Tracing.Tracer.LogValue("Modified objects in OS", modified?.Count ?? 0);
}
Profilers:
  • dotMemory (JetBrains) — best for XAF object tracking
  • PerfView (Microsoft, free) — .NET GC and heap analysis
  • Application Insights — production memory monitoring

csharp
// 开启XAF追踪来监控ObjectSpace的创建/释放
Tracing.Tracer.Initialize(TraceLevel.Verbose,
    Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\xaf_trace.log");

// 内存快照(仅开发环境使用)
var before = GC.GetTotalMemory(false);
GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect();
Tracing.Tracer.LogValue("Retained after GC", GC.GetTotalMemory(true));

// ObjectSpace内部追踪(调试用反射)
public static void LogObjectSpaceStats(IObjectSpace os) {
    if (os is not BaseObjectSpace baseOs) return;
    var modified = typeof(BaseObjectSpace)
        .GetField("modifiedObjects", BindingFlags.NonPublic | BindingFlags.Instance)
        ?.GetValue(baseOs) as IDictionary;
    Tracing.Tracer.LogValue("Modified objects in OS", modified?.Count ?? 0);
}
性能分析工具:
  • dotMemory(JetBrains)——最适合XAF对象追踪
  • PerfView(微软免费工具)——.NET GC和堆分析
  • Application Insights——生产环境内存监控

Code Review Checklist

代码审查清单

✅ Every View.Event += in OnActivated has -= in OnDeactivated AND Dispose
✅ No static fields storing Controller, View, or IObjectSpace references
✅ All IObjectSpace created outside the view use `using` or explicit Dispose
✅ CollectionSource disposed in OnDeactivated and Dispose
✅ Large dataset operations use batching (new ObjectSpace per batch)
✅ No ObjectSpace stored in Session, Application, or singleton services
✅ Session_End handler disposes all IDisposable objects in Session (WebForms)
✅ Dispose(bool) calls base.Dispose(disposing) as the last statement
✅ Double-disposal safe (guard with bool _disposed)
✅ Disposal methods wrap each operation in try/catch to ensure full cleanup

✅ 所有在OnActivated中通过+=订阅的View事件,都在OnDeactivated和Dispose中有对应的-=取消订阅逻辑
✅ 没有存储Controller、View或IObjectSpace引用的静态字段
✅ 所有在视图外创建的IObjectSpace都使用`using`语句或显式调用Dispose
✅ CollectionSource在OnDeactivated和Dispose中被释放
✅ 大数据集操作使用批量处理(每个批次新建一个ObjectSpace)
✅ 没有将ObjectSpace存储在Session、Application或单例服务中
✅ Session_End处理逻辑会释放Session中所有IDisposable对象(WebForms场景)
✅ Dispose(bool)方法最后会调用base.Dispose(disposing)
✅ 支持重复释放安全(用bool _disposed做守卫)
✅ 释放方法中的每个操作都用try/catch包裹,确保完整清理

Source Links

来源链接