Skip to content

Commit 473287a

Browse files
authored
Merge pull request #166 from DanielTrommel/advanced-property-path
Support for more advanced path definitions
2 parents 1a324ab + 481003f commit 473287a

File tree

1 file changed

+101
-23
lines changed

1 file changed

+101
-23
lines changed

src/Extensions/ObjectExtensions.cs

Lines changed: 101 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
using System;
2+
using System.Collections;
23
using System.Reflection;
4+
using System.Text.RegularExpressions;
35

46
namespace WinUI.TableView.Extensions;
57

68
/// <summary>
79
/// Provides extension methods for object types.
810
/// </summary>
9-
internal static class ObjectExtensions
11+
internal static partial class ObjectExtensions
1012
{
13+
// Regex to split property paths into property names and indexers (for cases like e.g. "[2].Foo[0].Bar", where Foo might be a Property that returns an array)
14+
[GeneratedRegex(@"([^.[]+)|(\[[^\]]+\])", RegexOptions.Compiled)]
15+
private static partial Regex PropertyPathRegex();
16+
1117
/// <summary>
1218
/// Gets the value of a property from an object using a sequence of property info and index pairs.
1319
/// </summary>
@@ -16,14 +22,39 @@ internal static class ObjectExtensions
1622
/// <returns>The value of the property, or null if the object is null.</returns>
1723
internal static object? GetValue(this object? obj, (PropertyInfo pi, object? index)[] pis)
1824
{
19-
foreach (var pi in pis)
25+
foreach (var (pi, index) in pis)
2026
{
2127
if (obj is null)
22-
{
2328
break;
24-
}
2529

26-
obj = pi.index is not null ? pi.pi.GetValue(obj, [pi.index]) : pi.pi.GetValue(obj);
30+
if (pi != null)
31+
{
32+
// Use property getter, with or without index
33+
obj = index is not null ? pi.GetValue(obj, [index]) : pi.GetValue(obj);
34+
}
35+
else if (index is int i)
36+
{
37+
// Array
38+
if (obj is Array arr)
39+
{
40+
obj = arr.GetValue(i);
41+
}
42+
// IList
43+
else if (obj is IList list)
44+
{
45+
obj = list[i];
46+
}
47+
else
48+
{
49+
// Not a supported indexer type
50+
return null;
51+
}
52+
}
53+
else
54+
{
55+
// Not a supported path segment
56+
return null;
57+
}
2758
}
2859

2960
return obj;
@@ -39,41 +70,88 @@ internal static class ObjectExtensions
3970
/// <returns>The value of the property, or null if the object is null.</returns>
4071
internal static object? GetValue(this object? obj, Type? type, string? path, out (PropertyInfo pi, object? index)[] pis)
4172
{
42-
var parts = path?.Split('.');
73+
if (obj == null || string.IsNullOrWhiteSpace(path) || type == null)
74+
{
75+
pis = [];
76+
return obj;
77+
}
4378

44-
if (parts is null)
79+
var matches = PropertyPathRegex().Matches(path);
80+
if (matches.Count == 0)
4581
{
4682
pis = [];
4783
return obj;
4884
}
4985

50-
pis = new (PropertyInfo, object?)[parts.Length];
86+
// Pre-size the steps array to the number of matches
87+
pis = new (PropertyInfo, object?)[matches.Count];
88+
int i = 0;
89+
object? current = obj;
90+
Type? currentType = type;
5191

52-
for (var i = 0; i < parts.Length; i++)
92+
foreach (Match match in matches)
5393
{
54-
var part = parts[i];
55-
var index = default(object?);
94+
string part = match.Value;
95+
object? index = null;
96+
PropertyInfo? pi = null;
97+
5698
if (part.StartsWith('[') && part.EndsWith(']'))
5799
{
58-
index = int.TryParse(part[1..^1], out var ind) ? ind : index;
59-
part = "Item";
60-
}
100+
// Indexer: [index] or [key]
101+
string indexer = part[1..^1];
102+
if (int.TryParse(indexer, out int intIndex))
103+
index = intIndex;
104+
else
105+
index = indexer;
61106

62-
var pi = type?.GetProperty(part);
63-
if (pi is not null)
64-
{
65-
pis[i] = (pi, index);
66-
obj = index is not null ? pi?.GetValue(obj, [index]) : pi?.GetValue(obj);
67-
type = obj?.GetType();
107+
// Try array
108+
if (current is Array arr && index is int idx)
109+
{
110+
current = arr.GetValue(idx);
111+
pis[i++] = (null!, idx);
112+
currentType = current?.GetType();
113+
continue;
114+
}
115+
116+
// Try IList
117+
if (current is IList list && index is int idx2)
118+
{
119+
current = list[idx2];
120+
pis[i++] = (null!, idx2);
121+
currentType = current?.GetType();
122+
continue;
123+
}
124+
125+
// Try default indexer property (e.g., this[string])
126+
pi = currentType?.GetProperty("Item");
127+
if (pi != null)
128+
{
129+
current = pi.GetValue(current, [index]);
130+
pis[i++] = (pi, index);
131+
currentType = current?.GetType();
132+
continue;
133+
}
134+
135+
// Not found
136+
pis = null!;
137+
return null;
68138
}
69139
else
70140
{
71-
pis = null!;
72-
return null;
141+
// Property access
142+
pi = currentType?.GetProperty(part, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
143+
if (pi == null)
144+
{
145+
pis = null!;
146+
return null;
147+
}
148+
current = pi.GetValue(current);
149+
pis[i++] = (pi, null);
150+
currentType = current?.GetType();
73151
}
74152
}
75153

76-
return obj;
154+
return current;
77155
}
78156

79157
/// <summary>

0 commit comments

Comments
 (0)