Skip to content
NLNuvyntra Labs

Documentation

Memory and scale

Leak rules, Small / Mid / Large list guarantees, and host-process measurements.

Memory, leaks, and scale

ScaleList sizeGuarantee
Small200Cheap notify; per-item Add is acceptable
Mid5,000AddRange → one Reset
Large50,000Same batching; UI must virtualize

App data is not framework overhead. A 50,000-row product list occupies whatever the Product objects occupy. The framework is responsible for PropertyChanged allocations, CollectionChanged fan-out, whether popped ViewModels are collectable, and whether messengers and commands pin graphs.

  • ViewModel never holds Page. Lifecycle is a host behavior that unsubscribes on Unloaded.
  • MessageHub default subscribe is weak. The handler must use the recipient argument.
  • Commands are instance fields of the ViewModel and die with it. CanExecuteChanged is a weak event so a popped Button does not stay pinned.
  • Dispose cancels in-flight AsyncModelCommand work.
  • SetProperty equality-exits without allocating a new PropertyChangedEventArgs. PropertyChanged hops to IMainThread when a dispatcher is present.

Host-process measurements (2026-08-31, .NET 10 on macOS, not device RSS): unchanged SetProperty ≈ 5 ns; changed SetProperty ≈ 19 ns with cached EventArgs; AddRange of 50,000 items raises one CollectionChanged and about 525 KB for the int list backing store, not 50,000 EventArgs. Device RSS budgets are not measured yet.

dotnet test tests/Plugin.Maui.MVVMExpress.Core.Tests
dotnet run --project benchmarks/Plugin.Maui.MVVMExpress.Benchmarks -c Release -- --quick

Threading, AOT, and security

  • ObservableModel.SetProperty is not thread-safe. Property, command, and navigation notifications hop to IMainThread when MarshalNotifications is true (default).
  • Do not ConfigureAwait(false) then new Page() or Shell.GoToAsync. Navigators hop first; NavigationThread.EnsurePageFactoryOnMainThread throws if they do not.
  • Bind Button.Command to AsyncModelCommand only on 0.6.0-preview+. 0.5.0 raised CanExecuteChanged off the UI thread after ConfigureAwait(false).
  • ICommand.Execute (async void) never throws. Failures go to IErrorSink / IDialogs. ExecuteAsync still rethrows.
  • AsyncState updates are compare-exchange; PropertyChanged is marshalled when a dispatcher is present.
  • INavigator is serialized per window. Concurrent navigations queue or fail as Outcome.
  • Library pipeline code uses ConfigureAwait(false) except where the next step must run on the UI context.
  • Typed NavigateToAsync<TViewModel>() is the AOT path. UseMvvmExpress applies generated [Route] / [RequiresAuth] via a ModuleInitializer. Convention scan of *Page / *ViewModel is opt-in and annotated.
  • Call InitializeComponent() on App before resolving AppShell / pages (App(IServiceProvider) + CreateWindow).
  • ViewModels do not store secrets. Tokens belong in Plugin.Maui.SecureSession / SecureStoragePlus.
  • Authorization attributes only consult IAuthState supplied by the app.