Skip to content

Mark Color as immutable and make Font a readonly struct - #33824

Merged
StephaneDelcroix merged 5 commits into
net11.0from
feature/immutable-color-font
Feb 27, 2026
Merged

Mark Color as immutable and make Font a readonly struct#33824
StephaneDelcroix merged 5 commits into
net11.0from
feature/immutable-color-font

Conversation

@StephaneDelcroix

@StephaneDelcroix StephaneDelcroix commented Feb 1, 2026

Copy link
Copy Markdown
Contributor

Note

Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!

Description

This PR improves type safety and documents immutability for core Graphics types.

Intent

These changes enable future XAML Source Generator (XSG) enhancements. By explicitly marking types as immutable, the source generator can make better optimization decisions, such as:

  • Caching instances safely
  • Avoiding defensive copies
  • Generating more efficient code paths for immutable value assignments

Changes

  1. Color class: Added [ImmutableObject(true)] attribute

    • Color was already effectively immutable (all fields are readonly)
    • The attribute documents this intent for tooling and developers
  2. Font struct: Converted to readonly struct

    • Changed from struct to readonly struct
    • Changed properties from { get; private set; } to { get; }
    • The private set was only used in the constructor, so this is not a breaking change

Why

  • Enables XSG optimizations for immutable types
  • Better compiler optimizations for readonly struct
  • Documents immutability intent explicitly
  • Enables better static analysis and tooling support
  • No breaking changes for consumers

API Changes

None - these are non-breaking enhancements that formalize existing immutability guarantees.

Copilot AI review requested due to automatic review settings February 1, 2026 16:23
@StephaneDelcroix StephaneDelcroix added this to the .NET 11.0-preview1 milestone Feb 1, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR enhances type safety and documents immutability guarantees for two core Graphics types by adding explicit immutability markers.

Changes:

  • Added [ImmutableObject(true)] attribute to the Color class to document its existing immutability
  • Converted the Font struct from struct to readonly struct and changed auto-properties from { get; private set; } to { get; }

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
src/Graphics/src/Graphics/Color.cs Added ImmutableObjectAttribute to formalize Color's existing immutability contract
src/Graphics/src/Graphics/Font.cs Converted to readonly struct with get-only properties, matching patterns used in other MAUI readonly structs

Comment thread src/Graphics/src/Graphics/Color.cs Outdated
[DebuggerDisplay("Red={Red}, Green={Green}, Blue={Blue}, Alpha={Alpha}")]
[TypeConverter(typeof(Converters.ColorTypeConverter))]
[ImmutableObject(true)]
public class Color

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

For Immutability wouldn't make sense to change this to be record? I'm not sure if it will be a binary breaking change thought

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

record is C# syntax and doesn't alter the object signature, but implements IEquatable for free

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

but roslyn can detect that, so why not

simonrozsival
simonrozsival previously approved these changes Feb 9, 2026
Comment thread src/Graphics/src/Graphics/Color.cs Outdated
/// <summary>
/// Determines whether the specified <see cref="Color"/> is equal to the current color using byte-precision comparison.
/// </summary>
public virtual bool Equals(Color other)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm confused. Why isn't this override?

simonrozsival
simonrozsival previously approved these changes Feb 9, 2026

@simonrozsival simonrozsival left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ship it

@PureWeen PureWeen left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

build is failing

@StephaneDelcroix

Copy link
Copy Markdown
Contributor Author

Hi @PureWeen — the builds are now passing (macOS Debug/Release, Windows Debug/Release, Helix unit tests all green ✅). The only failures are 'Run Integration Tests Build macOS' and 'RunOnAndroid' which are pre-existing on the net11.0 branch baseline.

Regarding the record class Color change: this intentionally changes == from reference equality to value equality. This is the desired behavior — two Colors with the same RGBA values should be equal. The SourceGen optimization relies on this for resource deduplication.

Could you re-review when you get a chance?

@github-actions

github-actions Bot commented Feb 27, 2026

Copy link
Copy Markdown
Contributor

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 33824

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 33824"

- Add [ImmutableObject(true)] attribute to Color class to document its immutability
- Convert Font from struct to readonly struct
- Change Font properties from { get; private set; } to { get; }
- Change Color from class to record class for IEquatable<Color> support
- Add explicit Equals(Color) using ToInt() to preserve byte-precision semantics
- All equality paths (==, !=, Equals) use consistent int comparison
- Add PublicAPI.Unshipped.txt entries for compiler-generated record members
…rd class

CS8872: 'Color.Equals(Color)' must allow overriding because the containing
record is not sealed. The virtual modifier is mandatory for record class types.
…lor record

- GetHashCode() now returns ToInt() to be consistent with Equals(Color? other),
  which also uses ToInt() for byte-precision comparison. Previously GetHashCode()
  hashed raw float values while Equals() quantized to bytes, violating the
  contract (two equal Colors could have different hash codes).
- Equals(Color? other) now checks EqualityContract to properly distinguish
  Color from derived record types, matching standard record equality semantics.
- Parameter type changed from Color to Color? to match the record-generated
  Equals pattern and allow proper null handling.
- Updated PublicAPI.Unshipped.txt for all TFMs to reflect the nullable parameter.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@StephaneDelcroix
StephaneDelcroix force-pushed the feature/immutable-color-font branch from 18af7df to f6643c9 Compare February 27, 2026 15:20
@StephaneDelcroix
StephaneDelcroix merged commit 797df24 into net11.0 Feb 27, 2026
24 of 29 checks passed
@StephaneDelcroix
StephaneDelcroix deleted the feature/immutable-color-font branch February 27, 2026 19:36
@github-actions github-actions Bot locked and limited conversation to collaborators Mar 30, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants