diff --git a/src/Avalonia.Base/Controls/ResourceDictionary.cs b/src/Avalonia.Base/Controls/ResourceDictionary.cs index 70c3e884e88..748882a4508 100644 --- a/src/Avalonia.Base/Controls/ResourceDictionary.cs +++ b/src/Avalonia.Base/Controls/ResourceDictionary.cs @@ -147,6 +147,9 @@ public void AddDeferred(object key, Func factory) public void AddDeferred(object key, IDeferredContent deferredContent) => Add(key, deferredContent); + public void AddNotSharedDeferred(object key, IDeferredContent deferredContent) + => Add(key, new NotSharedDeferredItem(deferredContent)); + public void Clear() { if (_inner?.Count > 0) @@ -236,12 +239,16 @@ public bool TryGetValue(object key, out object? value) try { _lastDeferredItemKey = key; - _inner[key] = value = deferred.Build(null) switch + value = deferred.Build(null) switch { ITemplateResult t => t.Result, { } v => v, _ => null, }; + if (deferred is not NotSharedDeferredItem) + { + _inner[key] = value; + } } finally { @@ -250,7 +257,6 @@ public bool TryGetValue(object key, out object? value) } return true; } - value = null; return false; } @@ -376,5 +382,11 @@ private sealed class DeferredItem : IDeferredContent public DeferredItem(Func factory) => _factory = factory; public object? Build(IServiceProvider? serviceProvider) => _factory(serviceProvider); } + + private sealed class NotSharedDeferredItem(IDeferredContent deferredContent) : IDeferredContent + { + private readonly IDeferredContent _deferredContent = deferredContent ; + public object? Build(IServiceProvider? serviceProvider) => _deferredContent.Build(serviceProvider); + } } } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs index 84ca68f4973..81a174c6e24 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Visitors; +using XamlX; using XamlX.Ast; using XamlX.Emit; using XamlX.IL; @@ -22,23 +23,61 @@ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode nod if (pa.Property.DeclaringType == types.ResourceDictionary && pa.Property.Name == "Content" && ShouldBeDeferred(pa.Values[1])) { + IXamlMethod addMethod = TryGetSharedValue(pa.Values[1], out var isShared) && !isShared + ? types.ResourceDictionaryNotSharedDeferredAdd + : types.ResourceDictionaryDeferredAdd; + pa.Values[1] = new XamlDeferredContentNode(pa.Values[1], types.XamlIlTypes.Object, context.Configuration); pa.PossibleSetters = new List { - new XamlDirectCallPropertySetter(types.ResourceDictionaryDeferredAdd), + new XamlDirectCallPropertySetter(addMethod), }; } else if (pa.Property.Name == "Resources" && pa.Property.Getter?.ReturnType.Equals(types.IResourceDictionary) == true && ShouldBeDeferred(pa.Values[1])) { + IXamlMethod addMethod = TryGetSharedValue(pa.Values[1], out var isShared) && !isShared + ? types.ResourceDictionaryNotSharedDeferredAdd + : types.ResourceDictionaryDeferredAdd; + pa.Values[1] = new XamlDeferredContentNode(pa.Values[1], types.XamlIlTypes.Object, context.Configuration); pa.PossibleSetters = new List { - new AdderSetter(pa.Property.Getter, types.ResourceDictionaryDeferredAdd), + new AdderSetter(pa.Property.Getter, addMethod), }; } return node; + + bool TryGetSharedValue(IXamlAstValueNode valueNode, out bool value) + { + value = default; + if (valueNode is XamlAstConstructableObjectNode co) + { + // Try find x:Share directive + if (co.Children.Find(d => d is XamlAstXmlDirective { Namespace: XamlNamespaces.Xaml2006, Name: "Shared" }) is XamlAstXmlDirective sharedDirective) + { + if (sharedDirective.Values.Count == 1 && sharedDirective.Values[0] is XamlAstTextNode text) + { + if (bool.TryParse(text.Text, out var parseValue)) + { + // If the parser succeeds, remove the x:Share directive + co.Children.Remove(sharedDirective); + return true; + } + else + { + context.ReportTransformError("Invalid argument type for x:Shared directive.", node); + } + } + else + { + context.ReportTransformError("Invalid number of arguments for x:Shared directive.", node); + } + } + } + return false; + } } private static bool ShouldBeDeferred(IXamlAstValueNode node) @@ -71,7 +110,7 @@ private static bool ShouldBeDeferred(IXamlAstValueNode node) return true; } - + class AdderSetter : IXamlILOptimizedEmitablePropertySetter, IEquatable { private readonly IXamlMethod _getter; diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs index 8fc4cfff76c..679938e504d 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -118,6 +118,7 @@ sealed class AvaloniaXamlIlWellKnownTypes public IXamlType IResourceDictionary { get; } public IXamlType ResourceDictionary { get; } public IXamlMethod ResourceDictionaryDeferredAdd { get; } + public IXamlMethod ResourceDictionaryNotSharedDeferredAdd { get; } public IXamlMethod ResourceDictionaryEnsureCapacity { get; } public IXamlMethod ResourceDictionaryGetCount { get; } public IXamlType IThemeVariantProvider { get; } @@ -312,6 +313,9 @@ public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg) ResourceDictionary = cfg.TypeSystem.GetType("Avalonia.Controls.ResourceDictionary"); ResourceDictionaryDeferredAdd = ResourceDictionary.GetMethod("AddDeferred", XamlIlTypes.Void, true, XamlIlTypes.Object, cfg.TypeSystem.GetType("Avalonia.Controls.IDeferredContent")); + ResourceDictionaryNotSharedDeferredAdd = ResourceDictionary.GetMethod("AddNotSharedDeferred", XamlIlTypes.Void, true, XamlIlTypes.Object, + cfg.TypeSystem.GetType("Avalonia.Controls.IDeferredContent")); + ResourceDictionaryEnsureCapacity = ResourceDictionary.GetMethod("EnsureCapacity", XamlIlTypes.Void, true, XamlIlTypes.Int32); ResourceDictionaryGetCount = ResourceDictionary.GetMethod("get_Count", XamlIlTypes.Int32, true); IThemeVariantProvider = cfg.TypeSystem.GetType("Avalonia.Controls.IThemeVariantProvider"); diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XSharedDirectiveTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XSharedDirectiveTests.cs new file mode 100644 index 00000000000..2e04588c9f7 --- /dev/null +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XSharedDirectiveTests.cs @@ -0,0 +1,54 @@ +using Avalonia.Controls; +using Avalonia.UnitTests; +using Xunit; + +namespace Avalonia.Markup.Xaml.UnitTests.Xaml; + +public class XSharedDirectiveTests : XamlTestBase +{ + [Fact] + public void Should_Create_New_Instance_Where_x_Share_Is_False() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + const string xaml = $$""" + + + + + + + + + + + + + + + """; + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); + window.ApplyTemplate(); + + var implicitSharedInstance1 = window.FindResource("ImplicitSharedResource"); + Assert.NotNull(implicitSharedInstance1); + var implicitSharedInstance2 = window.FindResource("ImplicitSharedResource"); + Assert.NotNull(implicitSharedInstance2); + + Assert.Same(implicitSharedInstance1, implicitSharedInstance2); + + var notSharedResource1 = window.FindResource("NotSharedResource"); + Assert.NotNull(notSharedResource1); + + var notSharedResource2 = window.FindResource("NotSharedResource"); + Assert.NotNull(notSharedResource2); + + Assert.NotSame(notSharedResource1, notSharedResource2); + + Assert.Equal(notSharedResource1.ToString(), notSharedResource2.ToString()); + } + } +}