diff --git a/src/Components/Components/src/ChangeDetection.cs b/src/Components/Components/src/ChangeDetection.cs index 68918d023758..952debeb8f90 100644 --- a/src/Components/Components/src/ChangeDetection.cs +++ b/src/Components/Components/src/ChangeDetection.cs @@ -1,10 +1,23 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections.Concurrent; +using Microsoft.AspNetCore.Components.HotReload; + namespace Microsoft.AspNetCore.Components; internal sealed class ChangeDetection { + private static readonly ConcurrentDictionary _immutableObjectTypesCache = new(); + + static ChangeDetection() + { + if (HotReloadManager.Default.MetadataUpdateSupported) + { + HotReloadManager.Default.OnDeltaApplied += _immutableObjectTypesCache.Clear; + } + } + public static bool MayHaveChanged(T1 oldValue, T2 newValue) { var oldIsNotNull = oldValue != null; @@ -30,10 +43,6 @@ public static bool MayHaveChanged(T1 oldValue, T2 newValue) return false; } - // The contents of this list need to trade off false negatives against computation - // time. So we don't want a huge list of types to check (or would have to move to - // a hashtable lookup, which is differently expensive). It's better not to include - // uncommon types here even if they are known to be immutable. // This logic assumes that no new System.TypeCode enum entries have been declared since 7.0, or at least that any new ones // represent immutable types. If System.TypeCode changes, review this logic to ensure it is still correct. // Supported immutable types : bool, byte, sbyte, short, ushort, int, uint, long, ulong, char, double, @@ -41,6 +50,11 @@ public static bool MayHaveChanged(T1 oldValue, T2 newValue) // For performance reasons, the following immutable types are not supported: IntPtr, UIntPtr, Type. private static bool IsKnownImmutableType(Type type) => Type.GetTypeCode(type) != TypeCode.Object - || type == typeof(Guid) + || _immutableObjectTypesCache.GetOrAdd(type, IsImmutableObjectTypeCore); + + private static bool IsImmutableObjectTypeCore(Type type) + => type == typeof(Guid) + || type == typeof(DateOnly) + || type == typeof(TimeOnly) || typeof(IEventCallback).IsAssignableFrom(type); } diff --git a/src/Components/Components/test/RenderTreeDiffBuilderTest.cs b/src/Components/Components/test/RenderTreeDiffBuilderTest.cs index aebcfcd4b692..4d495f690b47 100644 --- a/src/Components/Components/test/RenderTreeDiffBuilderTest.cs +++ b/src/Components/Components/test/RenderTreeDiffBuilderTest.cs @@ -1736,6 +1736,9 @@ public void SkipsUpdatingParametersOnChildComponentsIfAllAreDefinitelyImmutableA // Arrange: Populate old and new with equivalent content RenderFragment fragmentWillNotChange = builder => throw new NotImplementedException(); var dateTimeWillNotChange = DateTime.Now; + var dateOnlyWillNotChange = DateOnly.FromDateTime(dateTimeWillNotChange); + var timeOnlyWillNotChange = TimeOnly.FromDateTime(dateTimeWillNotChange); + foreach (var tree in new[] { oldTree, newTree }) { tree.OpenComponent(0); @@ -1758,6 +1761,8 @@ public void SkipsUpdatingParametersOnChildComponentsIfAllAreDefinitelyImmutableA tree.AddComponentParameter(1, "MyEnum", StringComparison.OrdinalIgnoreCase); tree.AddComponentParameter(1, "MyEventCallback", EventCallback.Empty); tree.AddComponentParameter(1, "MyEventCallbackOfT", EventCallback.Empty); + tree.AddComponentParameter(1, "MyDateOnly", dateOnlyWillNotChange); + tree.AddComponentParameter(1, "MyTimeOnly", timeOnlyWillNotChange); tree.CloseComponent(); }