Browse Source

Make ItemsSourceView ctors private.

So in future we could have different `ItemSourceView` concrete implementations that e.g. have an inner `IReadOnlyList<T>` in order to address #8764.
Steven Kirk 2 years ago
parent
commit
518391b37d

+ 1 - 1
src/Avalonia.Controls/ItemsControl.cs

@@ -96,7 +96,7 @@ namespace Avalonia.Controls
         /// </summary>
         public ItemsControl()
         {
-            _itemsView = new(_items);
+            _itemsView = ItemsSourceView.GetOrCreate(_items);
             _itemsView.PostCollectionChanged += ItemsCollectionChanged;
             UpdatePseudoClasses(0);
         }

+ 46 - 25
src/Avalonia.Controls/ItemsSourceView.cs

@@ -34,7 +34,7 @@ namespace Avalonia.Controls
         /// Initializes a new instance of the ItemsSourceView class for the specified data source.
         /// </summary>
         /// <param name="source">The data source.</param>
-        public ItemsSourceView(IEnumerable source)
+        private protected ItemsSourceView(IEnumerable source)
         {
             _inner = source switch
             {
@@ -166,6 +166,48 @@ namespace Avalonia.Controls
             };
         }
 
+        /// <summary>
+        /// Gets or creates an <see cref="ItemsSourceView{T}"/> for the specified enumerable.
+        /// </summary>
+        /// <param name="items">The enumerable.</param>
+        /// <remarks>
+        /// This method handles the following three cases:
+        /// - If <paramref name="items"/> is null, returns <see cref="Empty"/>
+        /// - If <paramref name="items"/> is an <see cref="ItemsSourceView"/> returns the existing
+        ///   <see cref="ItemsSourceView"/>
+        /// - Otherwise creates a new <see cref="ItemsSourceView"/>
+        /// </remarks>
+        public static ItemsSourceView<T> GetOrCreate<T>(IEnumerable? items)
+        {
+            return items switch
+            {
+                ItemsSourceView<T> isv => isv,
+                null => ItemsSourceView<T>.Empty,
+                _ => new ItemsSourceView<T>(items)
+            };
+        }
+
+        /// <summary>
+        /// Gets or creates an <see cref="ItemsSourceView{T}"/> for the specified enumerable.
+        /// </summary>
+        /// <param name="items">The enumerable.</param>
+        /// <remarks>
+        /// This method handles the following three cases:
+        /// - If <paramref name="items"/> is null, returns <see cref="Empty"/>
+        /// - If <paramref name="items"/> is an <see cref="ItemsSourceView"/> returns the existing
+        ///   <see cref="ItemsSourceView"/>
+        /// - Otherwise creates a new <see cref="ItemsSourceView"/>
+        /// </remarks>
+        public static ItemsSourceView<T> GetOrCreate<T>(IEnumerable<T>? items)
+        {
+            return items switch
+            {
+                ItemsSourceView<T> isv => isv,
+                null => ItemsSourceView<T>.Empty,
+                _ => new ItemsSourceView<T>(items)
+            };
+        }
+
         public IEnumerator<object?> GetEnumerator()
         {
             static IEnumerator<object> EnumerateItems(IList list)
@@ -210,7 +252,7 @@ namespace Avalonia.Controls
         internal string KeyFromIndex(int index) => throw new NotImplementedException();
     }
 
-    public class ItemsSourceView<T> : ItemsSourceView, IReadOnlyList<T>
+    public sealed class ItemsSourceView<T> : ItemsSourceView, IReadOnlyList<T>
     {
         /// <summary>
         ///  Gets an empty <see cref="ItemsSourceView"/>
@@ -221,12 +263,12 @@ namespace Avalonia.Controls
         /// Initializes a new instance of the ItemsSourceView class for the specified data source.
         /// </summary>
         /// <param name="source">The data source.</param>
-        public ItemsSourceView(IEnumerable<T> source)
+        internal ItemsSourceView(IEnumerable<T> source)
             : base(source)
         {
         }
 
-        private ItemsSourceView(IEnumerable source)
+        internal ItemsSourceView(IEnumerable source)
             : base(source)
         {
         }
@@ -263,26 +305,5 @@ namespace Avalonia.Controls
         }
 
         IEnumerator IEnumerable.GetEnumerator() => Inner.GetEnumerator();
-
-        /// <summary>
-        /// Gets or creates an <see cref="ItemsSourceView{T}"/> for the specified enumerable.
-        /// </summary>
-        /// <param name="items">The enumerable.</param>
-        /// <remarks>
-        /// This method handles the following three cases:
-        /// - If <paramref name="items"/> is null, returns <see cref="Empty"/>
-        /// - If <paramref name="items"/> is an <see cref="ItemsSourceView"/> returns the existing
-        ///   <see cref="ItemsSourceView"/>
-        /// - Otherwise creates a new <see cref="ItemsSourceView"/>
-        /// </remarks>
-        public static new ItemsSourceView<T> GetOrCreate(IEnumerable? items)
-        {
-            return items switch
-            {
-                ItemsSourceView<T> isv => isv,
-                null => Empty,
-                _ => new ItemsSourceView<T>(items)
-            };
-        }
     }
 }

+ 1 - 1
src/Avalonia.Controls/Repeater/ItemsRepeater.cs

@@ -442,7 +442,7 @@ namespace Avalonia.Controls
                     var newDataSource = newEnumerable as ItemsSourceView;
                     if (newEnumerable != null && newDataSource == null)
                     {
-                        newDataSource = new ItemsSourceView(newEnumerable);
+                        newDataSource = ItemsSourceView.GetOrCreate(newEnumerable);
                     }
 
                     OnDataSourcePropertyChanged(ItemsSourceView, newDataSource);

+ 1 - 1
src/Avalonia.Controls/Selection/SelectionNodeBase.cs

@@ -24,7 +24,7 @@ namespace Avalonia.Controls.Selection
                     if (ItemsView?.Inner is INotifyCollectionChanged inccOld)
                         CollectionChangedEventManager.Instance.RemoveListener(inccOld, this);
                     _source = value;
-                    ItemsView = value is object ? ItemsSourceView<T>.GetOrCreate(value) : null;
+                    ItemsView = value is object ? ItemsSourceView.GetOrCreate<T>(value) : null;
                     if (ItemsView?.Inner is INotifyCollectionChanged inccNew)
                         CollectionChangedEventManager.Instance.AddListener(inccNew, this);
                 }

+ 2 - 9
tests/Avalonia.Controls.UnitTests/ItemsSourceViewTests.cs

@@ -15,7 +15,7 @@ namespace Avalonia.Controls.UnitTests
         public void Only_Subscribes_To_Source_CollectionChanged_When_CollectionChanged_Subscribed()
         {
             var source = new AvaloniaList<string>();
-            var target = new ItemsSourceView<string>(source);
+            var target = ItemsSourceView.GetOrCreate(source);
             var debug = (INotifyCollectionChangedDebug)source;
 
             Assert.Null(debug.GetCollectionChangedSubscribers());
@@ -31,18 +31,11 @@ namespace Avalonia.Controls.UnitTests
             Assert.Null(debug.GetCollectionChangedSubscribers());
         }
 
-        [Fact]
-        public void Cannot_Wrap_An_ItemsSourceView_In_Another()
-        {
-            var source = new ItemsSourceView<string>(new string[0]);
-            Assert.Throws<ArgumentException>(() => new ItemsSourceView<string>(source));
-        }
-
         [Fact]
         public void Cannot_Create_ItemsSourceView_With_Collection_That_Implements_INCC_But_Not_List()
         {
             var source = new InvalidCollection();
-            Assert.Throws<ArgumentException>(() => new ItemsSourceView<string>(source));
+            Assert.Throws<ArgumentException>(() => ItemsSourceView.GetOrCreate(source));
         }
 
         private class InvalidCollection : INotifyCollectionChanged, IEnumerable<string>