Project Windows Runtime struct fields as C# fields - #2505
Open
Sergio Pedri (Sergio0694) wants to merge 6 commits into
Open
Project Windows Runtime struct fields as C# fields#2505Sergio Pedri (Sergio0694) wants to merge 6 commits into
Sergio Pedri (Sergio0694) wants to merge 6 commits into
Conversation
Sergio Pedri (Sergio0694)
requested a review
from Manodasan Wignarajah (manodasanW)
August 1, 2026 20:31
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
Sergio Pedri (Sergio0694)
marked this pull request as ready for review
August 1, 2026 22:14
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
Projected structs were emitting each Windows Runtime struct field as a
'{ readonly get; set; }' auto-property. That turned out to be problematic,
most notably for authoring: the WinMD generator maps public instance fields
back to Windows Runtime struct fields, so an authored struct written against
the projected shape produced an empty struct in the generated metadata. It
also prevents callers from taking a reference to a member, and is simply
confusing, given Windows Runtime structs are plain data.
Emit plain public fields instead, declared before the constructor so that
declaration order (which drives the sequential layout of the struct) mirrors
the metadata order.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d136d263-1a79-45e8-8a92-fb6dd28b2d79
'Point', 'Rect', 'Size' and 'EventRegistrationToken' are manually projected in
'WinRT.Runtime', and were mirroring the Windows Runtime struct fields as
'{ readonly get; set; }' properties. Expose them as plain public fields, to
match how projected structs are now generated.
The negative-value validation that lived in the 'Rect.Width'/'Rect.Height' and
'Size.Width'/'Size.Height' setters moves into the constructors that document it
(this is also what CsWinRT 2.x did). The remaining members ('Left', 'Top',
'Right', 'Bottom', 'IsEmpty', 'Empty', ...) are managed-only conveniences and
stay as properties.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d136d263-1a79-45e8-8a92-fb6dd28b2d79
The custom-mapped XAML struct types are defined in full by their addition
files, and mirrored the Windows Runtime struct fields as properties over
private backing fields (a shape inherited from the UWP/WPF managed
projections). Now that generated structs project their fields as C# fields,
these would otherwise be inconsistent with their siblings: 'Thickness',
'Matrix', 'GeneratorPosition' and 'Color' are generated types (so their
members are fields), while 'CornerRadius' and friends are hand-written.
Expose the actual metadata fields as public fields for:
- 'CornerRadius': TopLeft, TopRight, BottomRight, BottomLeft
- 'GridLength': Value, GridUnitType
- 'Duration': TimeSpan, Type
- 'KeyTime': TimeSpan
- 'RepeatBehavior': Count, Duration, Type
- 'Matrix3D': M11-M34, OffsetX, OffsetY, OffsetZ, M44
Field declaration order (and therefore the sequential layout each of these
blittable structs marshals with) is unchanged. Managed-only members are left
untouched: 'GridLength.IsAbsolute'/'IsAuto'/'IsStar'/'Auto',
'Duration.HasTimeSpan'/'Automatic'/'Forever', 'RepeatBehavior.HasCount'/
'HasDuration'/'Forever', 'Matrix3D.Identity'/'IsIdentity'/'HasInverse'/... and
all the operators, conversions and formatting helpers.
Validation that lived in the removed property setters is preserved where the
constructors already performed it ('CornerRadius.Validate', 'GridLength',
'KeyTime.FromTimeSpan'). 'GridLength' and 'KeyTime' are no longer 'readonly
struct'-s, since Windows Runtime structs are mutable data.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d136d263-1a79-45e8-8a92-fb6dd28b2d79
Refreshes the 'EventRegistrationToken' snippet in the event infrastructure doc, and documents the projection rule in the CsWinRT 3.0 spec (renaming the neighbouring 'Point'/'Rect'/'Size' section, which was already about their fields rather than properties). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: d136d263-1a79-45e8-8a92-fb6dd28b2d79
Both comments justified skipping ABI struct emission / per-field marshalling for mapped structs by claiming their projected public fields don't match the WinMD field layout. That is no longer accurate now that the addition files expose the metadata fields directly. The actual reason is that these types are defined in full by an addition file rather than generated from metadata, so they're passed through by value and only get BoxToUnmanaged/UnboxToManaged. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: d136d263-1a79-45e8-8a92-fb6dd28b2d79
Negative GridLength values are a valid pattern, and are used by real apps (e.g. the Microsoft Store) for custom layout panels, so the constructors should not reject them. This drops the whole value validation, since the fields are public and mutable now anyway, which made the check trivially bypassable. The IsFinite helper it was the only user of goes away with it. The GridUnitType validation is unchanged, and Auto still normalizes the value to the default. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Sergio Pedri (Sergio0694)
force-pushed
the
user/sergiopedri/project-struct-fields-as-fields
branch
from
August 12, 2026 21:07
a7ba75b to
b1d3a76
Compare
Sergio Pedri (Sergio0694)
enabled auto-merge (squash)
August 12, 2026 21:08
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Projects the fields of Windows Runtime struct types as C# fields instead of
{ readonly get; set; }properties, across the projection writer, the manually projected types inWinRT.Runtime, and the custom-mapped XAML types defined by addition files.Motivation
Projecting struct fields as properties turned out to be problematic, and confusing.
The biggest issue is authoring.
cswinrtwinmdgen.exemaps the public instance fields of an authoredstructto Windows Runtime struct fields, which is the only shape that can round-trip: an author writing a struct against the projected shape (properties) would silently produce an empty struct in the generated.winmd. Projecting fields as fields makes consumption and authoring symmetrical: the shape you consume is exactly the shape you author.Beyond authoring, Windows Runtime structs are plain data — there is no accessor to run any logic behind, so a property only obscures that, and it prevents callers from taking a reference to a member (e.g. to pass it as a
refargument, or to mutate an element of an array of structs in place). This also matches what C++/WinRT does, and what CsWinRT 2.x did for generated struct projections.Changes
Projection writer
src/WinRT.Projection.Writer/Builders/ProjectionFileBuilder.cs:WriteStructnow emits a public field per Windows Runtime struct field, declared before the constructor so declaration order (which drives the sequential layout of the struct) mirrors the metadata order.src/WinRT.Projection.Writer/Factories/AbiStructFactory.cs,src/WinRT.Projection.Writer/Factories/StructEnumMarshallerFactory.cs: refresh comments that justified the mapped-struct special cases by claiming their projected public fields don't match the WinMD field layout, which is no longer accurate.Manually projected types
src/WinRT.Runtime2/Windows.Foundation/Point.cs,Rect.cs,Size.cs,src/WinRT.Runtime2/InteropServices/Events/EventRegistrationToken.cs:X,Y,Width,HeightandValuebecome public fields. The negative-value validation that lived in theRect/Sizesetters moves into the constructors that document it (which is also what CsWinRT 2.x did). Managed-only members (Left,Top,Right,Bottom,IsEmpty,Empty, ...) are unchanged.Custom-mapped XAML types (addition files, both the
Microsoft.UI.XamlandWindows.UI.Xamlcopies)…/Microsoft.UI.Xaml.CornerRadius.cs:TopLeft,TopRight,BottomRight,BottomLeft…/Microsoft.UI.Xaml.GridLength.cs:Value,GridUnitType…/Microsoft.UI.Xaml.Duration.cs:TimeSpan,Type…/Microsoft.UI.Xaml.Media.Animation.KeyTime.cs:TimeSpan…/Microsoft.UI.Xaml.Media.Animation.RepeatBehavior.cs:Count,Duration,Type…/Microsoft.UI.Xaml.Media.Media3D.Matrix3D.cs:M11-M34,OffsetX,OffsetY,OffsetZ,M44These types are defined in full by their addition files, so they kept the property-over-private-backing-field shape inherited from the UWP/WPF managed projections. Leaving them alone would have made them inconsistent with their own siblings, since
Thickness,Matrix,GeneratorPositionandColorare generated types in CsWinRT 3.0 and therefore now expose fields. Field declaration order (and therefore the sequential layout each of these blittable structs marshals with) is unchanged, and validation that lived in the removed setters is preserved where the constructors already performed it.GridLengthandKeyTimeare no longerreadonly struct-s, since Windows Runtime structs are mutable data. Members that are not Windows Runtime struct fields are left untouched (GridLength.IsAbsolute/IsAuto/IsStar/Auto,Duration.HasTimeSpan/Automatic/Forever,RepeatBehavior.HasCount/HasDuration/Forever,Matrix3D.Identity/IsIdentity/HasInverse, and all operators, conversions and formatting helpers).There is one deliberate behavior change on top of that: the
GridLengthconstructors no longer reject negative (or non-finite) values. NegativeGridLengthvalues turn out to be a valid pattern that real apps rely on (e.g. the Microsoft Store uses them in several custom layout panels), and now thatValueis a public mutable field the check was trivially bypassable anyway. TheGridUnitTypevalidation is unchanged,GridUnitType.Autostill normalizes the value to the default, and theIsFinitehelper the removed check was the only user of goes away with it.Tests and docs
src/Tests/ProjectionWriterTest/Test_ProjectedStructs.cs: new tests asserting that projected struct fields are emitted as C# fields, and that no projected struct member is emitted as an auto-property, in both projection modes.docs/cswinrt3.0-spec.md: documents the projection rule, and renames the neighbouringPoint/Rect/Sizesection (which was already about their fields rather than properties).docs/event-infrastructure.md: refreshes theEventRegistrationTokensnippet.Validation
ProjectionWriterTest(16),WinMDGeneratorTest(17) andSourceGenerator2Test(129) all pass.Windows,Windows.UI.XamlandWinAppSDKprojections were regenerated end-to-end and compile cleanly, which covers the emitted structs, their marshallers, and all of the addition files.GridLengthaddition files were compiled standalone and exercised to confirm negative,NaNand infinite values now construct successfully, thatGridUnitType.Autostill normalizes to the default, and that an out-of-rangeGridUnitTypestill throws.