Fix issue 14187: PropertyGrid.SelectedObjects shows empty grid with typed arrays when PropertySort is NoSort (.NET 9/10 regression)#14190
Conversation
There was a problem hiding this comment.
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 Report❌ Patch coverage is 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
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
KlausLoeffelmann
left a comment
There was a problem hiding this comment.
@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! |
|
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. 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. |
| else | ||
| { | ||
| List<PropertyDescriptor[]>? properties = GetCommonProperties(objects.AsSpan(1), presort: true, tab, parentEntry); | ||
| if (objects.Length == 0) |
There was a problem hiding this comment.
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?
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: This led to a type mismatch between
|
There was a problem hiding this comment.
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!


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
Customer Impact
Regression?
Risk
Screenshots
Before
Before.mp4
After
After.mp4
Test methodology
Test environment(s)
Microsoft Reviewers: Open in CodeFlow