diff --git a/src/Uno.Extensions.Reactive/Core/ListState.T.cs b/src/Uno.Extensions.Reactive/Core/ListState.T.cs index c49a54f5e6..daa70dbb60 100644 --- a/src/Uno.Extensions.Reactive/Core/ListState.T.cs +++ b/src/Uno.Extensions.Reactive/Core/ListState.T.cs @@ -64,11 +64,19 @@ public static IListState Empty(TOwner owner, [CallerMemberName] strin /// Type of the owner of the state. /// The owner of the state. /// The provider of the initial value of the state. + /// The caller member name, used as a stable cache key. + /// The caller line number, used as a stable cache key. /// A feed that encapsulate the source. - public static IListState Value(TOwner owner, Func> valueProvider) + public static IListState Value(TOwner owner, Func> valueProvider, [CallerMemberName] string? name = null, [CallerLineNumber] int line = -1) where TOwner : class - // Note: We force the usage of delegate so 2 properties which are doing State.Value(this, () => 42) will effectively have 2 distinct states. - => AttachedProperty.GetOrCreate(owner, valueProvider, static (o, v) => SourceContext.GetOrCreate(o).CreateListState(Option>.Some(v()))); + // Use CallerMemberName+line as stable cache key instead of delegate reference identity. + // Delegate instances can be recreated after MetadataUpdater.ApplyUpdate on WASM, + // which would cause cache misses and state recreation (spec 033). + => AttachedProperty.GetOrCreate>, IListState>( + owner, + (name ?? throw new InvalidOperationException("The name of the list state must not be null"), line < 0 ? throw new InvalidOperationException("The provided line number is invalid.") : line), + valueProvider, + static (o, _, v) => SourceContext.GetOrCreate(o).CreateListState(Option>.Some(v()))); /// /// Gets or creates a list state from a static initial list of items. @@ -76,11 +84,16 @@ public static IListState Value(TOwner owner, Func> /// Type of the owner of the state. /// The owner of the state. /// The provider of the initial value of the state. + /// The caller member name, used as a stable cache key. + /// The caller line number, used as a stable cache key. /// A feed that encapsulate the source. - public static IListState Value(TOwner owner, Func> valueProvider) + public static IListState Value(TOwner owner, Func> valueProvider, [CallerMemberName] string? name = null, [CallerLineNumber] int line = -1) where TOwner : class - // Note: We force the usage of delegate so 2 properties which are doing State.Value(this, () => 42) will effectively have 2 distinct states. - => AttachedProperty.GetOrCreate(owner, valueProvider, static (o, v) => SourceContext.GetOrCreate(o).CreateListState(Option>.Some(v()))); + => AttachedProperty.GetOrCreate>, IListState>( + owner, + (name ?? throw new InvalidOperationException("The name of the list state must not be null"), line < 0 ? throw new InvalidOperationException("The provided line number is invalid.") : line), + valueProvider, + static (o, _, v) => SourceContext.GetOrCreate(o).CreateListState(Option>.Some(v()))); /// /// Gets or creates a list state from a static initial list of items. diff --git a/src/Uno.Extensions.Reactive/Core/ListState.cs b/src/Uno.Extensions.Reactive/Core/ListState.cs index 9c3a8b27ac..0d255d97cf 100644 --- a/src/Uno.Extensions.Reactive/Core/ListState.cs +++ b/src/Uno.Extensions.Reactive/Core/ListState.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using System.Runtime.CompilerServices; using System.Threading; namespace Uno.Extensions.Reactive; @@ -34,11 +35,12 @@ public static IListState Create(TOwner owner, FuncThe type of the value of the resulting feed. /// The owner of the state. /// The provider of the initial value of the state. + /// The caller member name, used as a stable cache key. + /// The caller line number, used as a stable cache key. /// A state that encapsulate the source. - public static IListState Value(TOwner owner, Func> valueProvider) + public static IListState Value(TOwner owner, Func> valueProvider, [CallerMemberName] string? name = null, [CallerLineNumber] int line = -1) where TOwner : class - // Note: We force the usage of delegate so 2 properties which are doing State.Value(this, () => 42) will effectively have 2 distinct states. - => ListState.Value(owner, valueProvider); + => ListState.Value(owner, valueProvider, name, line); /// /// Gets or creates a list state from a static initial list of items. @@ -47,11 +49,12 @@ public static IListState Value(TOwner owner, FuncThe type of the value of the resulting feed. /// The owner of the state. /// The provider of the initial value of the state. + /// The caller member name, used as a stable cache key. + /// The caller line number, used as a stable cache key. /// A state that encapsulate the source. - public static IListState Value(TOwner owner, Func> valueProvider) + public static IListState Value(TOwner owner, Func> valueProvider, [CallerMemberName] string? name = null, [CallerLineNumber] int line = -1) where TOwner : class - // Note: We force the usage of delegate so 2 properties which are doing State.Value(this, () => 42) will effectively have 2 distinct states. - => ListState.Value(owner, valueProvider); + => ListState.Value(owner, valueProvider, name, line); /// /// Gets or creates a list state from an async method.