@@ -22,7 +22,7 @@ namespace Uno.UI.DataBinding
22
22
[ DebuggerDisplay ( "Path={_path} DataContext={_dataContext}" ) ]
23
23
internal class BindingPath : IDisposable , IValueChangedListener
24
24
{
25
- private static List < PropertyChangedRegistrationHandler > _propertyChangedHandlers = new List < PropertyChangedRegistrationHandler > ( ) ;
25
+ private static List < IPropertyChangedRegistrationHandler > _propertyChangedHandlers = new List < IPropertyChangedRegistrationHandler > ( 2 ) ;
26
26
private readonly string _path ;
27
27
28
28
private BindingItem ? _chain ;
@@ -31,13 +31,40 @@ internal class BindingPath : IDisposable, IValueChangedListener
31
31
private bool _disposed ;
32
32
33
33
/// <summary>
34
- /// Defines a delegate that will create a registration on the specified <paramref name="dataContext"/>> for the specified <paramref name="propertyName"/>.
34
+ /// Defines a interface that will allow for the creation of a registration on the specified dataContext
35
+ /// for the specified propertyName.
35
36
/// </summary>
36
- /// <param name="dataContext">The datacontext to use</param>
37
- /// <param name="propertyName">The property in the datacontext</param>
38
- /// <param name="onNewValue">The action to execute when a new value is raised</param>
39
- /// <returns>A disposable that will cleanup resources.</returns>
40
- public delegate IDisposable ? PropertyChangedRegistrationHandler ( ManagedWeakReference dataContext , string propertyName , Action onNewValue ) ;
37
+ public interface IPropertyChangedRegistrationHandler
38
+ {
39
+ /// <summary>
40
+ /// Registere a new <see cref="IPropertyChangedValueHandler"/> for the specified property
41
+ /// </summary>
42
+ /// <param name="dataContext">The datacontext to use</param>
43
+ /// <param name="propertyName">The property in the datacontext</param>
44
+ /// <param name="onNewValue">The action to execute when a new value is raised</param>
45
+ /// <returns>A disposable that will cleanup resources.</returns>
46
+ IDisposable ? Register ( ManagedWeakReference dataContext , string propertyName , IPropertyChangedValueHandler onNewValue ) ;
47
+ }
48
+
49
+ /// <summary>
50
+ /// PropertyChanged value handler.
51
+ /// </summary>
52
+ /// <remarks>
53
+ /// This is an interface to avoid the use of delegates, and delegates type conversion as
54
+ /// there are two available signatures. (<see cref="Action"/> and <see cref="DependencyPropertyChangedCallback"/>)
55
+ /// </remarks>
56
+ public interface IPropertyChangedValueHandler
57
+ {
58
+ /// <summary>
59
+ /// Process a property changed using the <see cref="DependencyPropertyChangedCallback"/> signature.
60
+ /// </summary>
61
+ void NewValue ( DependencyObject dependencyObject , DependencyPropertyChangedEventArgs args ) ;
62
+
63
+ /// <summary>
64
+ /// Processa a property changed using <see cref="Action"/>-like signature (e.g. for <see cref="BindingItem"/>)
65
+ /// </summary>
66
+ void NewValue ( ) ;
67
+ }
41
68
42
69
/// <summary>
43
70
/// Provides the new values for the current binding.
@@ -49,7 +76,7 @@ internal class BindingPath : IDisposable, IValueChangedListener
49
76
50
77
static BindingPath ( )
51
78
{
52
- RegisterPropertyChangedRegistrationHandler ( SubscribeToNotifyPropertyChanged ) ;
79
+ RegisterPropertyChangedRegistrationHandler ( new BindingPathPropertyChangedRegistrationHandler ( ) ) ;
53
80
}
54
81
55
82
/// <summary>
@@ -122,7 +149,7 @@ internal void CloneShareableObjectsInPath()
122
149
/// <remarks>This method exists to provide layer separation,
123
150
/// when BindingPath is in the presentation layer, and DependencyProperty is in the (some) Views layer.
124
151
/// </remarks>
125
- public static void RegisterPropertyChangedRegistrationHandler ( PropertyChangedRegistrationHandler handler )
152
+ public static void RegisterPropertyChangedRegistrationHandler ( IPropertyChangedRegistrationHandler handler )
126
153
{
127
154
_propertyChangedHandlers . Add ( handler ) ;
128
155
}
@@ -279,6 +306,15 @@ protected virtual void Dispose(bool disposing)
279
306
}
280
307
}
281
308
309
+ /// <summary>
310
+ /// Property changed registration handler for BindingPath.
311
+ /// </summary>
312
+ private class BindingPathPropertyChangedRegistrationHandler : IPropertyChangedRegistrationHandler
313
+ {
314
+ public IDisposable ? Register ( ManagedWeakReference dataContext , string propertyName , IPropertyChangedValueHandler onNewValue )
315
+ => SubscribeToNotifyPropertyChanged ( dataContext , propertyName , onNewValue ) ;
316
+ }
317
+
282
318
#region Miscs helpers
283
319
/// <summary>
284
320
/// Parse the given string path in parts and create the linked list of binding items in head and tail
@@ -374,7 +410,7 @@ private static void TryPrependItem(
374
410
/// <summary>
375
411
/// Subscribes for updates to the INotifyPropertyChanged interface.
376
412
/// </summary>
377
- private static IDisposable ? SubscribeToNotifyPropertyChanged ( ManagedWeakReference dataContextReference , string propertyName , Action newValueAction )
413
+ private static IDisposable ? SubscribeToNotifyPropertyChanged ( ManagedWeakReference dataContextReference , string propertyName , IPropertyChangedValueHandler propertyChangedValueHandler )
378
414
{
379
415
// Attach to the Notify property changed events
380
416
var notify = dataContextReference . Target as System . ComponentModel . INotifyPropertyChanged ;
@@ -386,7 +422,7 @@ private static void TryPrependItem(
386
422
propertyName = "Item" + propertyName ;
387
423
}
388
424
389
- var newValueActionWeak = Uno . UI . DataBinding . WeakReferencePool . RentWeakReference ( null , newValueAction ) ;
425
+ var newValueActionWeak = Uno . UI . DataBinding . WeakReferencePool . RentWeakReference ( null , propertyChangedValueHandler ) ;
390
426
391
427
System . ComponentModel . PropertyChangedEventHandler handler = ( s , args ) =>
392
428
{
@@ -397,9 +433,10 @@ private static void TryPrependItem(
397
433
typeof ( BindingPath ) . Log ( ) . Debug ( $ "Property changed for { propertyName } on [{ dataContextReference . Target ? . GetType ( ) } ]") ;
398
434
}
399
435
400
- if ( ! newValueActionWeak . IsDisposed )
436
+ if ( ! newValueActionWeak . IsDisposed
437
+ && newValueActionWeak . Target is IPropertyChangedValueHandler handler )
401
438
{
402
- ( newValueActionWeak . Target as Action ) ? . Invoke ( ) ;
439
+ handler . NewValue ( ) ;
403
440
}
404
441
}
405
442
} ;
@@ -808,22 +845,14 @@ private IDisposable SubscribeToPropertyChanged()
808
845
for ( var i = 0 ; i < _propertyChangedHandlers . Count ; i ++ )
809
846
{
810
847
var handler = _propertyChangedHandlers [ i ] ;
811
- object ? previousValue = default ;
812
848
813
- Action ? updateProperty = ( ) =>
814
- {
815
- var newValue = GetSourceValue ( ) ;
816
-
817
- OnPropertyChanged ( previousValue , newValue , shouldRaiseValueChanged : true ) ;
818
-
819
- previousValue = newValue ;
820
- } ;
849
+ var valueHandler = new PropertyChangedValueHandler ( this ) ;
821
850
822
- var handlerDisposable = handler ( _dataContextWeakStorage ! , PropertyName , updateProperty ) ;
851
+ var handlerDisposable = handler . Register ( _dataContextWeakStorage ! , PropertyName , valueHandler ) ;
823
852
824
853
if ( handlerDisposable != null )
825
854
{
826
- previousValue = GetSourceValue ( ) ;
855
+ valueHandler . PreviousValue = GetSourceValue ( ) ;
827
856
828
857
// We need to keep the reference to the updatePropertyHandler
829
858
// in this disposable. The reference is attached to the source's
@@ -833,7 +862,9 @@ private IDisposable SubscribeToPropertyChanged()
833
862
// weak with regards to the delegates that are provided.
834
863
disposables . Add ( ( ) =>
835
864
{
836
- updateProperty = null ;
865
+ var previousValue = valueHandler . PreviousValue ;
866
+
867
+ valueHandler = null ;
837
868
handlerDisposable . Dispose ( ) ;
838
869
OnPropertyChanged ( previousValue , DependencyProperty . UnsetValue , shouldRaiseValueChanged : false ) ;
839
870
} ) ;
@@ -848,6 +879,43 @@ public void Dispose()
848
879
_disposed = true ;
849
880
_propertyChanged . Dispose ( ) ;
850
881
}
882
+
883
+ /// <summary>
884
+ /// Property changed value handler, used to avoid creating a delegate for processing
885
+ /// </summary>
886
+ /// <remarks>
887
+ /// This class is primarily used to avoid the costs associated with creating, storing and invoking delegates,
888
+ /// particularly on WebAssembly as of .NET 6 where invoking a delegate requires a context switch from AOT
889
+ /// to the interpreter.
890
+ /// </remarks>
891
+ private class PropertyChangedValueHandler : IPropertyChangedValueHandler , IWeakReferenceProvider
892
+ {
893
+ private readonly BindingItem _owner ;
894
+ private readonly ManagedWeakReference _self ;
895
+
896
+ public PropertyChangedValueHandler ( BindingItem owner )
897
+ {
898
+ _owner = owner ;
899
+ _self = WeakReferencePool . RentSelfWeakReference ( this ) ;
900
+ }
901
+
902
+ public object ? PreviousValue { get ; set ; }
903
+
904
+ public ManagedWeakReference WeakReference
905
+ => _self ;
906
+
907
+ public void NewValue ( )
908
+ {
909
+ var newValue = _owner . GetSourceValue ( ) ;
910
+
911
+ _owner . OnPropertyChanged ( PreviousValue , newValue , shouldRaiseValueChanged : true ) ;
912
+
913
+ PreviousValue = newValue ;
914
+ }
915
+
916
+ public void NewValue ( DependencyObject dependencyObject , DependencyPropertyChangedEventArgs args )
917
+ => NewValue ( ) ;
918
+ }
851
919
}
852
920
}
853
921
}
0 commit comments