11using System ;
22using System . Collections . Generic ;
3+ using System . Linq ;
34
45#if IOS || MACCATALYST
56using PlatformView = UIKit . UIView ;
@@ -15,13 +16,16 @@ namespace Microsoft.Maui
1516{
1617 public abstract class PropertyMapper : IPropertyMapper
1718 {
19+ // TODO: Make this private in .NET10
1820 protected readonly Dictionary < string , Action < IElementHandler , IElement > > _mapper = new ( StringComparer . Ordinal ) ;
1921
2022 IPropertyMapper [ ] ? _chained ;
2123
22- // Keep a distinct list of the keys so we don't run any duplicate (overridden) updates more than once
23- // when we call UpdateProperties
24- HashSet < string > ? _updateKeys ;
24+ IReadOnlyDictionary < string , Action < IElementHandler , IElement > > ? _mergedMappers ;
25+ private protected IReadOnlyDictionary < string , Action < IElementHandler , IElement > > MergedMappers => _mergedMappers ?? SnapshotMappers ( ) . Mappers ;
26+
27+ IReadOnlyList < string > ? _mergedKeys ;
28+ IReadOnlyList < string > MergedKeys => _mergedKeys ?? SnapshotMappers ( ) . Keys ;
2529
2630 public PropertyMapper ( )
2731 {
@@ -35,29 +39,48 @@ public PropertyMapper(params IPropertyMapper[]? chained)
3539 protected virtual void SetPropertyCore ( string key , Action < IElementHandler , IElement > action )
3640 {
3741 _mapper [ key ] = action ;
42+
3843 ClearKeyCache ( ) ;
3944 }
4045
46+ // TODO: Remove in .NET10
4147 protected virtual void UpdatePropertyCore ( string key , IElementHandler viewHandler , IElement virtualView )
4248 {
4349 if ( ! viewHandler . CanInvokeMappers ( ) )
50+ {
4451 return ;
52+ }
4553
46- var action = GetProperty ( key ) ;
47- action ? . Invoke ( viewHandler , virtualView ) ;
54+ TryUpdatePropertyCore ( key , viewHandler , virtualView ) ;
55+ }
56+
57+ internal bool TryUpdatePropertyCore ( string key , IElementHandler viewHandler , IElement virtualView )
58+ {
59+ if ( MergedMappers . TryGetValue ( key , out var action ) )
60+ {
61+ action ( viewHandler , virtualView ) ;
62+ return true ;
63+ }
64+
65+ return false ;
4866 }
4967
5068 public virtual Action < IElementHandler , IElement > ? GetProperty ( string key )
5169 {
5270 if ( _mapper . TryGetValue ( key , out var action ) )
71+ {
5372 return action ;
54- else if ( Chained is not null )
73+ }
74+
75+ if ( Chained is not null )
5576 {
5677 foreach ( var ch in Chained )
5778 {
5879 var returnValue = ch . GetProperty ( key ) ;
5980 if ( returnValue != null )
81+ {
6082 return returnValue ;
83+ }
6184 }
6285 }
6386
@@ -66,20 +89,32 @@ protected virtual void UpdatePropertyCore(string key, IElementHandler viewHandle
6689
6790 public void UpdateProperty ( IElementHandler viewHandler , IElement ? virtualView , string property )
6891 {
69- if ( virtualView == null )
92+ if ( virtualView == null || ! viewHandler . CanInvokeMappers ( ) )
93+ {
7094 return ;
95+ }
7196
72- UpdatePropertyCore ( property , viewHandler , virtualView ) ;
97+ if ( MergedMappers . TryGetValue ( property , out var action ) )
98+ {
99+ action ( viewHandler , virtualView ) ;
100+ }
73101 }
74102
75103 public void UpdateProperties ( IElementHandler viewHandler , IElement ? virtualView )
76104 {
77- if ( virtualView == null )
105+ if ( virtualView == null || ! viewHandler . CanInvokeMappers ( ) )
106+ {
78107 return ;
108+ }
79109
80- foreach ( var key in UpdateKeys )
110+ UpdatePropertiesCore ( viewHandler , virtualView ) ;
111+ }
112+
113+ private protected virtual void UpdatePropertiesCore ( IElementHandler viewHandler , IElement virtualView )
114+ {
115+ foreach ( var mapper in MergedMappers )
81116 {
82- UpdatePropertyCore ( key , viewHandler , virtualView ) ;
117+ mapper . Value ( viewHandler , virtualView ) ;
83118 }
84119 }
85120
@@ -93,35 +128,55 @@ public IPropertyMapper[]? Chained
93128 }
94129 }
95130
96- private HashSet < string > PopulateKeys ( )
97- {
98- var keys = new HashSet < string > ( StringComparer . Ordinal ) ;
99- foreach ( var key in GetKeys ( ) )
100- {
101- keys . Add ( key ) ;
102- }
103- return keys ;
104- }
105-
131+ // TODO: Make private in .NET10 with a new name: ClearMergedMappers
106132 protected virtual void ClearKeyCache ( )
107133 {
108- _updateKeys = null ;
134+ _mergedMappers = null ;
135+ _mergedKeys = null ;
109136 }
110137
111- public virtual IReadOnlyCollection < string > UpdateKeys => _updateKeys ??= PopulateKeys ( ) ;
138+ // TODO: Remove in .NET10
139+ public virtual IReadOnlyCollection < string > UpdateKeys => MergedKeys ;
112140
113141 public virtual IEnumerable < string > GetKeys ( )
114142 {
115- foreach ( var key in _mapper . Keys )
116- yield return key ;
117-
143+ // We want to retain the initial order of the keys to avoid race conditions
144+ // when a property mapping is overridden by a new instance of property mapper.
145+ // As an example, the container view mapper should always run first.
146+ // Siblings mapper should not have keys intersection.
118147 if ( Chained is not null )
119148 {
120- foreach ( var chain in Chained )
121- foreach ( var key in chain . GetKeys ( ) )
149+ for ( int i = Chained . Length - 1 ; i >= 0 ; i -- )
150+ {
151+ foreach ( var key in Chained [ i ] . GetKeys ( ) )
152+ {
122153 yield return key ;
154+ }
155+ }
156+ }
157+
158+ // Enqueue keys from this mapper.
159+ foreach ( var mapper in _mapper )
160+ {
161+ yield return mapper . Key ;
123162 }
124163 }
164+
165+ private ( List < string > Keys , Dictionary < string , Action < IElementHandler , IElement > > Mappers ) SnapshotMappers ( )
166+ {
167+ var keys = GetKeys ( ) . Distinct ( ) . ToList ( ) ;
168+
169+ var mappers = new Dictionary < string , Action < IElementHandler , IElement > > ( keys . Count ) ;
170+ foreach ( var key in keys )
171+ {
172+ mappers [ key ] = GetProperty ( key ) ! ;
173+ }
174+
175+ _mergedKeys = keys ;
176+ _mergedMappers = mappers ;
177+
178+ return ( keys , mappers ) ;
179+ }
125180 }
126181
127182 public interface IPropertyMapper
@@ -169,12 +224,22 @@ public void Add(string key, Action<TViewHandler, TVirtualView> action) =>
169224 SetPropertyCore ( key , ( h , v ) =>
170225 {
171226 if ( v is TVirtualView vv )
227+ {
172228 action ? . Invoke ( ( TViewHandler ) h , vv ) ;
229+ }
173230 else if ( Chained != null )
174231 {
175232 foreach ( var chain in Chained )
176233 {
177- if ( chain . GetProperty ( key ) != null )
234+ // Try to leverage our internal method which uses merged mappers
235+ if ( chain is PropertyMapper propertyMapper )
236+ {
237+ if ( propertyMapper . TryUpdatePropertyCore ( key , h , v ) )
238+ {
239+ break ;
240+ }
241+ }
242+ else if ( chain . GetProperty ( key ) != null )
178243 {
179244 chain . UpdateProperty ( h , v , key ) ;
180245 break ;
0 commit comments