Skip to content

Fix issue 14187: PropertyGrid.SelectedObjects shows empty grid with typed arrays when PropertySort is NoSort (.NET 9/10 regression)#14190

Merged
SimonZhao888 merged 3 commits intodotnet:mainfrom
SimonZhao888:Fix_Issue_14187
Jan 21, 2026
Merged

Fix issue 14187: PropertyGrid.SelectedObjects shows empty grid with typed arrays when PropertySort is NoSort (.NET 9/10 regression)#14190
SimonZhao888 merged 3 commits intodotnet:mainfrom
SimonZhao888:Fix_Issue_14187

Conversation

@SimonZhao888
Copy link
Copy Markdown
Member

@SimonZhao888 SimonZhao888 commented Jan 7, 2026

Fixes #14187

Root Cause

PropertyGrid.SelectedObjects accepts a covariant array (for example, CustomTypeDescriptor[]), and during subsequent property-merging logic, the internal code treats this array as object?[] and attempts to write elements into it. Because the CLR enforces runtime type checks on covariant arrays, writing an element whose type does not match the underlying array’s actual element type triggers an ArrayTypeMismatchException.
Span’s invariance prevents converting a covariant array directly into Span<object?>, but this is not the fundamental cause of the exception—the root issue is unsafe writes to a covariant array. The GetMergedProperties method returning null is a secondary symptom of the error chain, not the root cause.

Proposed changes

  • Added boundary check for empty array.
  • Created intermediate object?[] arrays to enable proper type covariance.
  • Simplified single-object handling.
  • Added explanatory comments about why this workaround is necessary.

Customer Impact

  • PropertyGrid.SelectedObjects shows correctly grid with typed arrays when PropertySort is NoSort.

Regression?

  • Yes

Risk

  • Min

Screenshots

Before

Before.mp4

After

After.mp4

Test methodology

  • Manually

Test environment(s)

  • .net 11.0.100-alpha.1.25618.104
Microsoft Reviewers: Open in CodeFlow

Copy link
Copy Markdown
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 PR fixes a .NET 9/10 regression where PropertyGrid.SelectedObjects displays an empty grid when using typed arrays with PropertySort set to NoSort. The issue stems from Span enforcing exact type matching, which causes ArrayTypeMismatchException when working with typed arrays like ItemTypeDescriptor[].

Key Changes:

  • Added empty array boundary check to prevent null reference issues
  • Introduced intermediate object?[] arrays to work around Span covariance limitations
  • Restructured single-object and multi-object handling logic

@codecov
Copy link
Copy Markdown

codecov Bot commented Jan 7, 2026

Codecov Report

❌ Patch coverage is 65.75342% with 25 lines in your changes missing coverage. Please review.
✅ Project coverage is 77.16063%. Comparing base (15506ad) to head (57967f6).
⚠️ Report is 18 commits behind head on main.

Additional details and impacted files
@@                 Coverage Diff                 @@
##                main      #14190         +/-   ##
===================================================
+ Coverage   77.15242%   77.16063%   +0.00820%     
===================================================
  Files           3279        3279                 
  Lines         645333      645403         +70     
  Branches       47720       47726          +6     
===================================================
+ Hits          497890      497997        +107     
+ Misses        143757      143717         -40     
- Partials        3686        3689          +3     
Flag Coverage Δ
Debug 77.16063% <65.75342%> (+0.00820%) ⬆️
integration 18.97305% <0.00000%> (-0.01119%) ⬇️
production 52.03420% <30.00000%> (+0.01874%) ⬆️
test 97.40478% <90.69767%> (-0.00082%) ⬇️
unit 49.49544% <30.00000%> (+0.05369%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown
Member

@KlausLoeffelmann KlausLoeffelmann left a comment

Choose a reason for hiding this comment

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

@SimonZhao888, can you please address the Copilot comments, before I review?

Also, a question:
Could you comment on what the introduction of nullablility regressed and how?

Copy link
Copy Markdown
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

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

@SimonZhao888
Copy link
Copy Markdown
Member Author

@SimonZhao888, can you please address the Copilot comments, before I review?

Also, a question: Could you comment on what the introduction of nullablility regressed and how?

OK, got it!

@SimonZhao888
Copy link
Copy Markdown
Member Author

Nullable Introduction Regression (What regressed and how)

After introducing nullable annotations, the type signature of SelectedObjects changed from object[] to object?[]. This change affected generic inference and internal handling in the following ways:

Previously, the code assumed SelectedObjects was always a “real object[]” and that writes would not trigger type mismatch issues.
With object?[], covariant arrays (such as CustomTypeDescriptor[]) more easily pass compiler type checks because CustomTypeDescriptor[] can covariantly convert to object?[].
As a result, the runtime now encounters type mismatches during write operations, causing ArrayTypeMismatchException—a scenario that was less likely under the non-nullable signature because the compiler imposed stricter type constraints.

In short, nullable annotations widened the API’s accepted type range, making it easier for covariant arrays to enter the code path and exposing a latent risk of unsafe writes.

Copy link
Copy Markdown
Member

@ricardobossan ricardobossan left a comment

Choose a reason for hiding this comment

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

All LGTM!

else
{
List<PropertyDescriptor[]>? properties = GetCommonProperties(objects.AsSpan(1), presort: true, tab, parentEntry);
if (objects.Length == 0)
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.

To fix the previous Regression, the following changes should be sufficient:

object[] sortObjects = new object[length - 1];
Array.Copy(objects, 1, sortObjects, 0, length - 1);
List<PropertyDescriptor[]>? properties = GetCommonProperties(sortObjects, presort: true, tab, parentEntry);

// This will work for just one as well.
List<PropertyDescriptor[]>? firstProperties = GetCommonProperties([objects[0]], presort: false, tab, parentEntry);

As for the added logic of handling objects.Length==0 and objects.Length==1 differently, It's necessary to determine whether a judgment on Lenght is necessary based on the context. If so, it can be placed in a separate PR to make the PR easier to review and to facilitate regression testing focusing on the current issue.

@KlausLoeffelmann What do you think?

@LeafShi1
Copy link
Copy Markdown
Member

LeafShi1 commented Jan 13, 2026

Could you comment on what the introduction of nullablility regressed and how?

I don't think this is a problem caused by introducing nullability, it's simply that the following modifications were made in PR #10420 while also introducing nullability:
image

This led to a type mismatch between array.GetType() and typeof(T[]) during the execution of the AsSpan method, resulting in the ArrayTypeMismatchException.

image

Copy link
Copy Markdown
Member

@KlausLoeffelmann KlausLoeffelmann left a comment

Choose a reason for hiding this comment

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

I agree with the assessment.

AsSpan(1) on an object[] works fine.

But if objects itself is, say, a Control[] or some other typed array that got passed in as object[], you're dealing with array covariance, and AsSpan doesn't play nicely with that.

Span<T> is invariant!

If you have what we have, namely for example:

csharpControl[] controls = [button1, button2];
object[] objects = controls; // Covariance allows this
var span = objects.AsSpan(1); // !!ArrayTypeMismatchException!!

then the runtime sees "this is actually a Control[], not an object[]" and throws because Span<object> can't be created from Control[]. The type safety guarantees would be violated.

The old code with Array.Copy worked because is handles covariant arrays just fine at runtime. It's doing element-by-element copying with runtime type checks.

So yeah - someone got bitten by the "Span is faster!" enthusiasm without considering that PropertyGrid deals with all kinds of arrays coming from selection contexts...

Beautifully done - thanks a lot!

@SimonZhao888 SimonZhao888 merged commit 8fca32e into dotnet:main Jan 21, 2026
8 checks passed
@SimonZhao888 SimonZhao888 deleted the Fix_Issue_14187 branch January 21, 2026 02:13
@github-actions github-actions Bot locked and limited conversation to collaborators Feb 20, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-controls-PropertyGrid PropertyGrid and editor related issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PropertyGrid.SelectedObjects shows empty grid with typed arrays when PropertySort is NoSort (.NET 9/10 regression)

5 participants