diff --git a/src/Microsoft.Blazor/Microsoft.Blazor.csproj b/src/Microsoft.Blazor/Microsoft.Blazor.csproj
index 9f5c4f4abb..37d1f5cd0e 100644
--- a/src/Microsoft.Blazor/Microsoft.Blazor.csproj
+++ b/src/Microsoft.Blazor/Microsoft.Blazor.csproj
@@ -2,6 +2,7 @@
netstandard2.0
+ 7.2
diff --git a/src/Microsoft.Blazor/RenderTree/ArrayBuilder.cs b/src/Microsoft.Blazor/RenderTree/ArrayBuilder.cs
new file mode 100644
index 0000000000..b07445d878
--- /dev/null
+++ b/src/Microsoft.Blazor/RenderTree/ArrayBuilder.cs
@@ -0,0 +1,119 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Runtime.CompilerServices;
+
+namespace Microsoft.Blazor.RenderTree
+{
+ ///
+ /// Implements a list that uses an array of objects to store the elements.
+ ///
+ public class ArrayBuilder
+ {
+ private const int MinCapacity = 10;
+ private T[] _items;
+ private int _itemsInUse;
+
+ ///
+ /// Constructs a new instance of .
+ ///
+ public ArrayBuilder() : this(MinCapacity)
+ {
+ }
+
+ ///
+ /// Constructs a new instance of .
+ ///
+ public ArrayBuilder(int capacity)
+ {
+ _items = new T[capacity < MinCapacity ? MinCapacity : capacity];
+ _itemsInUse = 0;
+ }
+
+ ///
+ /// Gets the number of items.
+ ///
+ public int Count => _itemsInUse;
+
+ ///
+ /// Gets the underlying buffer.
+ ///
+ public T[] Buffer => _items;
+
+ ///
+ /// Appends a new item, automatically resizing the underlying array if necessary.
+ ///
+ /// The item to append.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Append(T item)
+ {
+ if (_itemsInUse == _items.Length)
+ {
+ SetCapacity(_itemsInUse * 2, preserveContents: true);
+ }
+
+ _items[_itemsInUse++] = item;
+ }
+
+ ///
+ /// Removes the last item.
+ ///
+ public void RemoveLast()
+ {
+ _itemsInUse--;
+ _items[_itemsInUse] = default(T); // Release to GC
+ }
+
+ ///
+ /// Marks the array as empty, also shrinking the underlying storage if it was
+ /// not being used to near its full capacity.
+ ///
+ public void Clear()
+ {
+ var previousItemsInUse = _itemsInUse;
+ _itemsInUse = 0;
+
+ if (_items.Length > previousItemsInUse * 1.5)
+ {
+ SetCapacity((previousItemsInUse + _items.Length) / 2, preserveContents: false);
+ }
+ else
+ {
+ Array.Clear(_items, 0, _itemsInUse); // Release to GC
+ }
+ }
+
+ ///
+ /// Produces an structure describing the current contents.
+ ///
+ /// The .
+ public ArrayRange ToRange()
+ => new ArrayRange(_items, _itemsInUse);
+
+ private void SetCapacity(int desiredCapacity, bool preserveContents)
+ {
+ if (desiredCapacity < _itemsInUse)
+ {
+ throw new ArgumentOutOfRangeException(nameof(desiredCapacity), $"The value cannot be less than {nameof(Count)}");
+ }
+
+ var newCapacity = desiredCapacity < MinCapacity ? MinCapacity : desiredCapacity;
+ if (newCapacity != _items.Length)
+ {
+ var newItems = new T[newCapacity];
+
+ if (preserveContents)
+ {
+ Array.Copy(_items, newItems, _itemsInUse);
+ }
+
+ _items = newItems;
+ }
+ else if (!preserveContents)
+ {
+ Array.Clear(_items, 0, _items.Length);
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.Blazor/RenderTree/ArrayRange.cs b/src/Microsoft.Blazor/RenderTree/ArrayRange.cs
new file mode 100644
index 0000000000..37e5e1351f
--- /dev/null
+++ b/src/Microsoft.Blazor/RenderTree/ArrayRange.cs
@@ -0,0 +1,45 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace Microsoft.Blazor.RenderTree
+{
+ ///
+ /// Represents a range of elements in an array that are in use.
+ ///
+ /// The array item type.
+ public readonly struct ArrayRange : IEnumerable, IEnumerable
+ {
+ ///
+ /// Gets the underlying array instance.
+ ///
+ public T[] Array { get; }
+
+ ///
+ /// Gets the number of items in the array that are considered to be in use.
+ ///
+ public int Count { get; }
+
+ ///
+ /// Constructs an instance of .
+ ///
+ /// The array.
+ /// The number of items in the array that are in use.
+ public ArrayRange(T[] array, int count)
+ {
+ Array = array;
+ Count = count;
+ }
+
+ ///
+ IEnumerator IEnumerable.GetEnumerator()
+ => ((IEnumerable)new ArraySegment(Array, 0, Count)).GetEnumerator();
+
+ ///
+ IEnumerator IEnumerable.GetEnumerator()
+ => ((IEnumerable)new ArraySegment(Array, 0, Count)).GetEnumerator();
+ }
+}
diff --git a/src/Microsoft.Blazor/RenderTree/RenderTreeBuilder.cs b/src/Microsoft.Blazor/RenderTree/RenderTreeBuilder.cs
index 83acd927a5..22327e818d 100644
--- a/src/Microsoft.Blazor/RenderTree/RenderTreeBuilder.cs
+++ b/src/Microsoft.Blazor/RenderTree/RenderTreeBuilder.cs
@@ -13,10 +13,8 @@ namespace Microsoft.Blazor.RenderTree
///
public class RenderTreeBuilder
{
- private const int MinBufferLength = 10;
private readonly Renderer _renderer;
- private RenderTreeNode[] _entries = new RenderTreeNode[100];
- private int _entriesInUse = 0;
+ private readonly ArrayBuilder _entries = new ArrayBuilder(10);
private readonly Stack _openElementIndices = new Stack();
private RenderTreeNodeType? _lastNonAttributeNodeType;
@@ -39,7 +37,7 @@ namespace Microsoft.Blazor.RenderTree
/// A value representing the type of the element.
public void OpenElement(int sequence, string elementName)
{
- _openElementIndices.Push(_entriesInUse);
+ _openElementIndices.Push(_entries.Count);
Append(RenderTreeNode.Element(sequence, elementName));
}
@@ -50,7 +48,7 @@ namespace Microsoft.Blazor.RenderTree
public void CloseElement()
{
var indexOfEntryBeingClosed = _openElementIndices.Pop();
- _entries[indexOfEntryBeingClosed].CloseElement(_entriesInUse - 1);
+ _entries.Buffer[indexOfEntryBeingClosed].CloseElement(_entries.Count - 1);
}
///
@@ -149,35 +147,21 @@ namespace Microsoft.Blazor.RenderTree
///
public void Clear()
{
- // If the previous usage of the buffer showed that we have allocated
- // much more space than needed, free up the excess memory
- var shrinkToLength = Math.Max(MinBufferLength, _entries.Length / 2);
- if (_entriesInUse < shrinkToLength)
- {
- Array.Resize(ref _entries, shrinkToLength);
- }
-
- _entriesInUse = 0;
+ _entries.Clear();
_openElementIndices.Clear();
_lastNonAttributeNodeType = null;
}
///
/// Returns the values that have been appended.
- /// The return value's is always zero.
///
- /// An array segment of values.
- public ArraySegment GetNodes() =>
- new ArraySegment(_entries, 0, _entriesInUse);
+ /// An array range of values.
+ public ArrayRange GetNodes() =>
+ _entries.ToRange();
private void Append(RenderTreeNode node)
{
- if (_entriesInUse == _entries.Length)
- {
- Array.Resize(ref _entries, _entries.Length * 2);
- }
-
- _entries[_entriesInUse++] = node;
+ _entries.Append(node);
var nodeType = node.NodeType;
if (nodeType != RenderTreeNodeType.Attribute)
diff --git a/src/Microsoft.Blazor/RenderTree/RenderTreeDiff.cs b/src/Microsoft.Blazor/RenderTree/RenderTreeDiff.cs
index 497ddc1231..094c7a58e0 100644
--- a/src/Microsoft.Blazor/RenderTree/RenderTreeDiff.cs
+++ b/src/Microsoft.Blazor/RenderTree/RenderTreeDiff.cs
@@ -9,22 +9,22 @@ namespace Microsoft.Blazor.RenderTree
/// Describes changes to a component's render tree between successive renders,
/// as well as the resulting state.
///
- public struct RenderTreeDiff
+ public readonly struct RenderTreeDiff
{
///
/// Gets the changes to the render tree since a previous state.
///
- public ArraySegment Edits { get; private set; }
+ public ArrayRange Edits { get; }
///
/// Gets the latest render tree. That is, the result of applying the
/// to the previous state.
///
- public ArraySegment CurrentState { get; private set; }
+ public ArrayRange CurrentState { get; }
internal RenderTreeDiff(
- ArraySegment entries,
- ArraySegment referenceTree)
+ ArrayRange entries,
+ ArrayRange referenceTree)
{
Edits = entries;
CurrentState = referenceTree;
diff --git a/src/Microsoft.Blazor/RenderTree/RenderTreeDiffComputer.cs b/src/Microsoft.Blazor/RenderTree/RenderTreeDiffComputer.cs
index d66eadd7a4..0ea51a3f37 100644
--- a/src/Microsoft.Blazor/RenderTree/RenderTreeDiffComputer.cs
+++ b/src/Microsoft.Blazor/RenderTree/RenderTreeDiffComputer.cs
@@ -7,29 +7,17 @@ namespace Microsoft.Blazor.RenderTree
{
internal class RenderTreeDiffComputer
{
- private const int MinBufferLength = 10;
- private RenderTreeEdit[] _entries = new RenderTreeEdit[10];
- private int _entriesInUse;
+ private readonly ArrayBuilder _entries = new ArrayBuilder(10);
public RenderTreeDiff ComputeDifference(
- ArraySegment oldTree,
- ArraySegment newTree)
+ ArrayRange oldTree,
+ ArrayRange newTree)
{
- _entriesInUse = 0;
+ _entries.Clear();
var siblingIndex = 0;
AppendDiffEntriesForRange(oldTree.Array, 0, oldTree.Count, newTree.Array, 0, newTree.Count, ref siblingIndex);
- // If the previous usage of the buffer showed that we have allocated
- // much more space than needed, free up the excess memory
- var shrinkToLength = Math.Max(MinBufferLength, _entries.Length / 2);
- if (_entriesInUse < shrinkToLength)
- {
- Array.Resize(ref _entries, shrinkToLength);
- }
-
- return new RenderTreeDiff(
- new ArraySegment(_entries, 0, _entriesInUse),
- newTree);
+ return new RenderTreeDiff(_entries.ToRange(), newTree);
}
private void AppendDiffEntriesForRange(
@@ -303,19 +291,15 @@ namespace Microsoft.Blazor.RenderTree
if (entry.Type == RenderTreeEditType.StepOut)
{
// If the preceding node is a StepIn, then the StepOut cancels it out
- if (_entriesInUse > 0 && _entries[_entriesInUse - 1].Type == RenderTreeEditType.StepIn)
+ var previousIndex = _entries.Count - 1;
+ if (previousIndex >= 0 && _entries.Buffer[previousIndex].Type == RenderTreeEditType.StepIn)
{
- _entriesInUse--;
+ _entries.RemoveLast();
return;
}
}
- if (_entriesInUse == _entries.Length)
- {
- Array.Resize(ref _entries, _entries.Length * 2);
- }
-
- _entries[_entriesInUse++] = entry;
+ _entries.Append(entry);
}
}
}
diff --git a/src/Microsoft.Blazor/Rendering/ComponentState.cs b/src/Microsoft.Blazor/Rendering/ComponentState.cs
index 0d5794fdac..e0cbdd4959 100644
--- a/src/Microsoft.Blazor/Rendering/ComponentState.cs
+++ b/src/Microsoft.Blazor/Rendering/ComponentState.cs
@@ -55,11 +55,11 @@ namespace Microsoft.Blazor.Rendering
_renderer.UpdateDisplay(_componentId, diff);
}
- private void EnsureChildComponentsInstantiated(ArraySegment renderTree)
+ private void EnsureChildComponentsInstantiated(ArrayRange renderTree)
{
var array = renderTree.Array;
- var offsetPlusCount = renderTree.Offset + renderTree.Count;
- for (var i = renderTree.Offset; i < offsetPlusCount; i++)
+ var count = renderTree.Count;
+ for (var i = 0; i < count; i++)
{
if (array[i].NodeType == RenderTreeNodeType.Component
&& array[i].Component == null)
diff --git a/test/Microsoft.Blazor.Build.Test/RazorCompilerTest.cs b/test/Microsoft.Blazor.Build.Test/RazorCompilerTest.cs
index 439d140ad3..81b6353dcf 100644
--- a/test/Microsoft.Blazor.Build.Test/RazorCompilerTest.cs
+++ b/test/Microsoft.Blazor.Build.Test/RazorCompilerTest.cs
@@ -336,7 +336,7 @@ namespace Microsoft.Blazor.Build.Test
=> node.NodeType != RenderTreeNodeType.Text
|| !string.IsNullOrWhiteSpace(node.TextContent);
- private static ArraySegment GetRenderTree(IComponent component)
+ private static ArrayRange GetRenderTree(IComponent component)
{
var treeBuilder = new RenderTreeBuilder(new TestRenderer());
component.BuildRenderTree(treeBuilder);
diff --git a/test/Microsoft.Blazor.Test/RenderTreeBuilderTest.cs b/test/Microsoft.Blazor.Test/RenderTreeBuilderTest.cs
index aebf92cd26..e2fa28cea3 100644
--- a/test/Microsoft.Blazor.Test/RenderTreeBuilderTest.cs
+++ b/test/Microsoft.Blazor.Test/RenderTreeBuilderTest.cs
@@ -31,7 +31,6 @@ namespace Microsoft.Blazor.Test
// Assert
var nodes = builder.GetNodes();
Assert.NotNull(nodes.Array);
- Assert.Equal(0, nodes.Offset);
Assert.Empty(nodes);
}
@@ -49,7 +48,6 @@ namespace Microsoft.Blazor.Test
// Assert
var nodes = builder.GetNodes();
- Assert.Equal(0, nodes.Offset);
Assert.Collection(nodes,
node => AssertNode.Text(node, "First item"),
node => AssertNode.Text(node, string.Empty),
@@ -69,7 +67,6 @@ namespace Microsoft.Blazor.Test
// Assert
var nodes = builder.GetNodes();
- Assert.Equal(0, nodes.Offset);
Assert.Collection(nodes,
node => AssertNode.Text(node, "1234"),
node => AssertNode.Text(node, string.Empty));
@@ -103,7 +100,7 @@ namespace Microsoft.Blazor.Test
// Assert
var nodes = builder.GetNodes();
Assert.Equal(2, nodes.Count);
- AssertNode.Element(nodes[1], "my element", 1);
+ AssertNode.Element(nodes.Array[1], "my element", 1);
}
[Fact]
@@ -122,7 +119,7 @@ namespace Microsoft.Blazor.Test
// Assert
var nodes = builder.GetNodes();
Assert.Equal(4, nodes.Count);
- AssertNode.Element(nodes[0], "my element", 2);
+ AssertNode.Element(nodes.Array[0], "my element", 2);
}
[Fact]
diff --git a/test/Microsoft.Blazor.Test/RendererTest.cs b/test/Microsoft.Blazor.Test/RendererTest.cs
index c6b1860b88..caf12e43a1 100644
--- a/test/Microsoft.Blazor.Test/RendererTest.cs
+++ b/test/Microsoft.Blazor.Test/RendererTest.cs
@@ -313,8 +313,8 @@ namespace Microsoft.Blazor.Test
private class TestRenderer : Renderer
{
- public IDictionary> RenderTreesByComponentId { get; }
- = new Dictionary>();
+ public IDictionary> RenderTreesByComponentId { get; }
+ = new Dictionary>();
public new int AssignComponentId(IComponent component)
=> base.AssignComponentId(component);