Skip to content

Conversation

@noraa-junker
Copy link
Collaborator

Summary of the Pull Request

SettingsUtils is initialized multiple times over the whole solution. This creates one singeltone instance (with the default settings), so it only has to be initialized once (and improve performance a bit with that)

PR Checklist

  • Closes: #xxx
  • Communication: I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected
  • Tests: Added/updated and all pass
  • Localization: All end-user-facing strings can be localized
  • Dev docs: Added/updated
  • New binaries: Added on the required places
  • Documentation updated: If checked, please file a pull request on our docs repo and link it here: #xxx

Detailed Description of the Pull Request / Additional comments

Validation Steps Performed

@vanzue vanzue requested a review from Copilot December 4, 2025 04:00
Copilot finished reviewing on behalf of vanzue December 4, 2025 04:04
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request introduces a singleton pattern for SettingsUtils by creating a global static instance called GlobalDefaultInstance. The purpose is to avoid repeatedly instantiating SettingsUtils throughout the solution, which should improve performance by reducing redundant object creation and initialization. The PR systematically replaces all new SettingsUtils() calls across the codebase with references to the new singleton instance.

Key Changes

  • Added GlobalDefaultInstance static property to SettingsUtils class that provides a pre-initialized singleton instance
  • Changed the parameterless constructor from public to private to encourage use of the singleton
  • Updated all production code (Settings UI, modules, preview handlers, DSC, etc.) to use GlobalDefaultInstance instead of creating new instances
  • Modified unit tests to use parameterized constructors for mocking while maintaining test isolation

Reviewed changes

Copilot reviewed 83 out of 83 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/settings-ui/Settings.UI.Library/SettingsUtils.cs Implements the singleton pattern with GlobalDefaultInstance property and makes the default constructor private
src/settings-ui/Settings.UI/ViewModels/*.cs Multiple ViewModels updated to use GlobalDefaultInstance
src/settings-ui/Settings.UI/SettingsXAML/Views/*.cs All page views updated to use GlobalDefaultInstance
src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/*.cs OOBE pages updated to use GlobalDefaultInstance
src/settings-ui/Settings.UI/SettingsXAML/Flyout/*.cs Flyout pages updated to use GlobalDefaultInstance
src/settings-ui/Settings.UI/Helpers/HotkeyConflictIgnoreHelper.cs Helper class updated to use GlobalDefaultInstance
src/settings-ui/Settings.UI.UnitTests/ViewModelTests/*.cs Test files updated to use parameterized constructors with FileSystem for mocking
src/settings-ui/Settings.UI.Library/Utilities/GetSettingCommandLineCommand.cs Command line utilities updated to use GlobalDefaultInstance
src/settings-ui/Settings.UI.Library/SettingsBackupAndRestoreUtils.cs Backup utilities updated to use GlobalDefaultInstance
src/modules/previewpane/*/Settings.cs Preview pane modules updated to use GlobalDefaultInstance
src/modules/poweraccent/PowerAccent.Core/**/*.cs PowerAccent module updated to use GlobalDefaultInstance
src/modules/peek/Peek./**/.cs Peek module updated to use GlobalDefaultInstance
src/modules/launcher/PowerLauncher/SettingsReader.cs PowerLauncher updated to use GlobalDefaultInstance
src/modules/launcher/Plugins/**/UtilityProvider.cs Launcher plugin updated to use GlobalDefaultInstance
src/modules/colorPicker/ColorPickerUI/Settings/UserSettings.cs ColorPicker module updated to use GlobalDefaultInstance
src/modules/awake/Awake/**/*.cs Awake module updated to use GlobalDefaultInstance
src/modules/Workspaces/WorkspacesEditor/**/*.cs Workspaces editor updated to use GlobalDefaultInstance
src/modules/PowerOCR/PowerOCR/Settings/UserSettings.cs PowerOCR module updated to use GlobalDefaultInstance
src/modules/MouseWithoutBorders/App/Class/Setting.cs MouseWithoutBorders module updated to use GlobalDefaultInstance
src/modules/MouseUtils/MouseJumpUI/Helpers/SettingsHelper.cs MouseJump utility updated to use GlobalDefaultInstance
src/modules/MeasureTool/MeasureToolUI/Settings.cs MeasureTool module updated to use GlobalDefaultInstance
src/modules/Hosts/Hosts/Settings/UserSettings.cs Hosts module updated to use GlobalDefaultInstance
src/dsc/v3/PowerToys.DSC/Models/FunctionData/SettingsFunctionData`1.cs DSC function data updated to use GlobalDefaultInstance
src/dsc/v3/PowerToys.DSC.UnitTests/SettingsResourceTests/SettingsResourceModuleTest`1.cs DSC test updated to use GlobalDefaultInstance
src/common/UITestAutomation/SettingsConfigHelper.cs UI test automation helper updated to use GlobalDefaultInstance
Comments suppressed due to low confidence (1)

src/settings-ui/Settings.UI.Library/SettingsUtils.cs:35

  • The constructor at line 27 is now private, but lines 32 and 37 still have public constructors. This means the singleton pattern is not fully enforced - consumers can still create new instances using the public constructors. If the intent is to create a true singleton, all constructors should be private except for a parameterized constructor used for testing/mocking.
        private SettingsUtils()
            : this(new FileSystem())
        {
        }

        public SettingsUtils(IFileSystem? fileSystem, JsonSerializerOptions? serializerOptions = null)
            : this(fileSystem?.File!, new SettingPath(fileSystem?.Directory, fileSystem?.Path), serializerOptions)
        {
        }

private readonly JsonSerializerOptions _serializerOptions;

public SettingsUtils()
public static SettingsUtils GlobalDefaultInstance { get; } = new SettingsUtils();
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Consider renaming GlobalDefaultInstance to just Default or Instance to follow more conventional singleton naming patterns (e.g., Lazy<T>.Value, Task.CompletedTask). The "Global" prefix is redundant for a static property, and "Default" is clear from context.

Copilot uses AI. Check for mistakes.
private readonly IFile _file;
private readonly ISettingsPath _settingsPath;
private readonly JsonSerializerOptions _serializerOptions;

Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The introduction of GlobalDefaultInstance should be documented. Consider adding XML documentation comments to explain:

  1. When to use GlobalDefaultInstance vs. the parameterized constructor
  2. That GlobalDefaultInstance uses the default FileSystem implementation
  3. That tests should use the public constructors with mocked dependencies instead of the global instance

Additionally, the documentation in doc/devdocs/core/settings/settings-implementation.md shows incorrect usage examples that should be updated to reflect this change.

Suggested change
/// <summary>
/// Provides a global default instance of <see cref="SettingsUtils"/> using the default <see cref="FileSystem"/> implementation.
/// <para>
/// <b>Usage:</b> Use <c>GlobalDefaultInstance</c> for production code where default file system access is required.
/// For unit tests or scenarios requiring custom or mocked dependencies, use the public constructors instead.
/// </para>
/// <para>
/// <b>Note:</b> <c>GlobalDefaultInstance</c> is not suitable for tests; always inject mocked <see cref="IFileSystem"/> and <see cref="JsonSerializerOptions"/> via the constructors.
/// </para>
/// </summary>

Copilot uses AI. Check for mistakes.
ISettingsUtilsMocks.GetStubSettingsUtils<ColorPickerSettings>().Object,
SettingsRepository<GeneralSettings>.GetInstance(ISettingsUtilsMocks.GetStubSettingsUtils<GeneralSettings>().Object),
SettingsRepository<ColorPickerSettings>.GetInstance(new SettingsUtils()),
SettingsRepository<ColorPickerSettings>.GetInstance(SettingsUtils.GlobalDefaultInstance),
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is mixing mocked settings with the global default instance. Line 68 uses a stubbed ISettingsUtils mock, but line 70 uses SettingsUtils.GlobalDefaultInstance, which will read from the actual file system. This inconsistency may cause test flakiness or failures.

Consider using the same stubbed mock for consistency:

SettingsRepository<ColorPickerSettings>.GetInstance(ISettingsUtilsMocks.GetStubSettingsUtils<ColorPickerSettings>().Object),

Or if the global instance was intentional, all three parameters should consistently use either mocks or the global instance.

Suggested change
SettingsRepository<ColorPickerSettings>.GetInstance(SettingsUtils.GlobalDefaultInstance),
SettingsRepository<ColorPickerSettings>.GetInstance(ISettingsUtilsMocks.GetStubSettingsUtils<ColorPickerSettings>().Object),

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants