Compiled binding path for subclasses - support for mixed collection#382
Conversation
… in the BindingPath string). Included test cases for collection of items with the same parent class, on which shared properties and indexers are defined. Signed-off-by: Daniël Trommel <d.s.trommel@gmail.com>
…tial naming slip-up from when I first build it (focussed on properties) Signed-off-by: Daniël Trommel <d.s.trommel@gmail.com>
…s the original Getter flow; when navigating to the final segment, the code is exactly the same. Only the final bit of code (get versus set) is different.
This refactor also exposed a subtle bug; GetCompiledValueSetter_ShouldNotSet_ForOutOfBoundsNonGenericListIndex failed with ArgumentOutOfRangeException. This was a latent bug in the setter that the old over-specialization was masking:
- For a property typed IList (non-generic ArrayList), the shared navigation correctly leaves current.Type as the interface IList (no over-specialization).
- The setter's bounds-check did current.Type.GetProperty("Count"), but Count is declared on the base ICollection interface — and reflection doesn't surface inherited members on interface types. So countProperty was null, the bounds check was skipped, and the raw indexer assignment threw.
- The old setter only "worked" by accident: it had converted current to the concrete ArrayList, where Count is found directly.
There was a problem hiding this comment.
Pull request overview
This pull request hardens the compiled binding-path getter/setter generation so it remains safe when evaluating rows from mixed collections (e.g., sibling subclasses), avoiding invalid runtime casts while keeping the fast compiled-expression approach.
Changes:
- Updates binding-path expression-tree generation to cast to the declaring type of each segment (instead of the first encountered runtime subtype) to support mixed sibling row types.
- Refactors setter generation to reuse the shared path-navigation logic used by the getter (only the final segment differs).
- Adds regression tests covering mixed sibling rows and nested subclass scenarios.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| src/Extensions/ObjectExtensions.cs | Adjusts compiled getter/setter expression generation to be mixed-collection-friendly and refactors shared navigation logic. |
| tests/ObjectExtensionsTests.cs | Adds new test coverage for mixed sibling row scenarios across property and indexer paths. |
Comments suppressed due to low confidence (1)
src/Extensions/ObjectExtensions.cs:526
- Null-checking
currentagainstnullwill throw during expression construction whencurrentis a non-nullable value type (struct) and the next segment returns a nullable or reference type. That makes binding paths likeMyStruct.RefPropfail to compile even though structs cannot be null. Skip the null-check (and the conditional) when the container expression is a non-nullable value type.
if (nextPropertyAccess.Type.IsValueType && !nextPropertyAccess.Type.IsNullableType())
{
// Value types cannot be null, so don't need to check for null, and we can directly assign the property access
current = nextPropertyAccess;
}
|
@w-ahmad : that suppressed comment does not make sense |
w-ahmad
left a comment
There was a problem hiding this comment.
LGTM! Thanks for the contribution @DanielTrommel
|
@w-ahmad : just noticed that the file contains some warnings; will look into those, and push those minor changes. If you already merge this, I will create a small PR for that |
If you mean the IL warnings, I don’t think we can omit them. Anyway, just let me know if you want me to leave this PR open. |
|
@w-ahmad: just pushed some minor changes - old naming, and some small comment improvements wrt to old naming. About the Warnings: We can only suppress the warnings about trimming; not sure it common to suppress those in these cases? There are quite a few, and obscure other potential warnings. Also: these warnings are about trimming, and since we are runtime compiling here, this code isnt AOT compatible anyhow. You'll need code generator(s) anyways (which also suggested here: #140 (comment)), I think you already worked on that. So, I think we can safely suppress these warnings? If wished, at class level we could add this: And further on, above |
|
I think we shouldn't suppress these warnings, as they let users know that TableView uses reflection. All we can do is use the provided supportive attributes to display meaningful warning messages in the logs. I understand your idea of using source generators to remove reflection from code, which would make the code fully AOT compatible. I experimented with source generators once but found some major breaking changes, so I postponed it for later, though I haven’t completely dropped the idea. see #349 |
|
Yes, understandable. This PR is imo then ready to be merged.
…On Thu, 25 Jun 2026, 11:27 Waheed Ahmad, ***@***.***> wrote:
*w-ahmad* left a comment (w-ahmad/WinUI.TableView#382)
<#382 (comment)>
I think we shouldn't suppress these warnings, as they let users know that
TableView uses reflection. All we can do is use the provided supportive
attributes to display meaningful warning messages in the logs.
I understand your idea of using source generators to remove reflection
from code, which would make the code fully AOT compatible. I experimented
with source generators once but found some major breaking changes, so I
postponed it for later, though I haven’t completely dropped the idea. see
#349 <#349>
—
Reply to this email directly, view it on GitHub
<#382?email_source=notifications&email_token=AEYV7OF22L5JTI4K74L7DRT5BTV6JA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTINZZG43TIOJQGE3KM4TFMFZW63VHNVSW45DJN5XKKZLWMVXHJLDGN5XXIZLSL5RWY2LDNM#issuecomment-4797749016>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AEYV7ODIBYHFWY4DBXIAFTL5BTV6JAVCNFSNUABFKJSXA33TNF2G64TZHM3TIOJVGUYDGOBXHNEXG43VMU5TINRUGQYDAMJTGQZKC5QC>
.
Triage notifications, keep track of coding agent tasks and review pull
requests on the go with GitHub Mobile for iOS
<https://github.com/notifications/mobile/ios/AEYV7OGFMKSZN63IMNCJTTL5BTV6JA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTINZZG43TIOJQGE3KM4TFMFZW63VHNVSW45DJN5XKKZLWMVXHJKTGN5XXIZLSL5UW64Y>
and Android
<https://github.com/notifications/mobile/android/AEYV7OGCA6N55MIR5QN5LST5BTV6JA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTINZZG43TIOJQGE3KM4TFMFZW63VHNVSW45DJN5XKKZLWMVXHJLTGN5XXIZLSL5QW4ZDSN5UWI>.
Download it today!
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
@w-ahmad , back again with an fix/improvement on the Func/BindingPath code.
Ref this PR, #178, in which we worked on generating a Func at runtime once, instead of repeated reflection to get a cell's value.
In the comments on that PR (#178 (comment)), I already mentioned that the current Func is built just using the first instance for which we happen do a GetPropertyValue(), which might cause trouble.
The specific problem: If the instances used for the rows in the table is a mixture of subclasses, and the (first) binding (step) is to an element on the shared parent class, the current code will throw an InvalidCastException when sorting.
The reason is that the first instance that is used to built the Func, dictates the explicit type cast that is done;
An example: suppose the type of the instance is MySubClass and for a column our BindingPath is "MyProperty", the current code in main generates this:
However, if MyProperty is actually defined on MySuperClass, that code should use a cast to that type instead:
This way, if the CollectionView contains a mixture of instance of different subclasses for MySuperClass, we won't get an InvalidCastException.
Furthermore:
main, I noticed you added a func for the Setter variant, so I also hardened your code so it will handle subclassingThe above points are in order and in separate commits.