Skip to content

Commit 0c39af9

Browse files
committed
Minor code refactoring
1 parent 6bfb9e1 commit 0c39af9

File tree

4 files changed

+271
-249
lines changed

4 files changed

+271
-249
lines changed
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Buffers;
7+
using System.Diagnostics.Contracts;
8+
using System.Runtime.CompilerServices;
9+
10+
namespace Microsoft.Toolkit.Mvvm.Messaging.Internals
11+
{
12+
/// <summary>
13+
/// A simple buffer writer implementation using pooled arrays.
14+
/// </summary>
15+
/// <typeparam name="T">The type of items to store in the list.</typeparam>
16+
/// <remarks>
17+
/// This type is a <see langword="ref"/> <see langword="struct"/> to avoid the object allocation and to
18+
/// enable the pattern-based <see cref="IDisposable"/> support. We aren't worried with consumers not
19+
/// using this type correctly since it's private and only accessible within the parent type.
20+
/// </remarks>
21+
internal ref struct ArrayPoolBufferWriter<T>
22+
{
23+
/// <summary>
24+
/// The default buffer size to use to expand empty arrays.
25+
/// </summary>
26+
private const int DefaultInitialBufferSize = 128;
27+
28+
/// <summary>
29+
/// The underlying <typeparamref name="T"/> array.
30+
/// </summary>
31+
private T[] array;
32+
33+
/// <summary>
34+
/// The starting offset within <see cref="array"/>.
35+
/// </summary>
36+
private int index;
37+
38+
/// <summary>
39+
/// Creates a new instance of the <see cref="ArrayPoolBufferWriter{T}"/> struct.
40+
/// </summary>
41+
/// <returns>A new <see cref="ArrayPoolBufferWriter{T}"/> instance.</returns>
42+
[Pure]
43+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
44+
public static ArrayPoolBufferWriter<T> Create()
45+
{
46+
return new() { array = ArrayPool<T>.Shared.Rent(DefaultInitialBufferSize) };
47+
}
48+
49+
/// <summary>
50+
/// Gets a <see cref="ReadOnlySpan{T}"/> with the current items.
51+
/// </summary>
52+
public ReadOnlySpan<T> Span
53+
{
54+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
55+
get => this.array.AsSpan(0, this.index);
56+
}
57+
58+
/// <summary>
59+
/// Adds a new item to the current collection.
60+
/// </summary>
61+
/// <param name="item">The item to add.</param>
62+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
63+
public void Add(T item)
64+
{
65+
if (this.index == this.array.Length)
66+
{
67+
ResizeBuffer();
68+
}
69+
70+
this.array[this.index++] = item;
71+
}
72+
73+
/// <summary>
74+
/// Resets the underlying array and the stored items.
75+
/// </summary>
76+
public void Reset()
77+
{
78+
Array.Clear(this.array, 0, this.index);
79+
80+
this.index = 0;
81+
}
82+
83+
/// <summary>
84+
/// Resizes <see cref="array"/> when there is no space left for new items.
85+
/// </summary>
86+
[MethodImpl(MethodImplOptions.NoInlining)]
87+
private void ResizeBuffer()
88+
{
89+
T[] rent = ArrayPool<T>.Shared.Rent(this.index << 2);
90+
91+
Array.Copy(this.array, 0, rent, 0, this.index);
92+
Array.Clear(this.array, 0, this.index);
93+
94+
ArrayPool<T>.Shared.Return(this.array);
95+
96+
this.array = rent;
97+
}
98+
99+
/// <inheritdoc cref="IDisposable.Dispose"/>
100+
public void Dispose()
101+
{
102+
Array.Clear(this.array, 0, this.index);
103+
104+
ArrayPool<T>.Shared.Return(this.array);
105+
}
106+
}
107+
}
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
#if NETSTANDARD2_0
6+
7+
using System;
8+
using System.Collections.Generic;
9+
using System.Diagnostics.Contracts;
10+
using System.Runtime.CompilerServices;
11+
12+
namespace Microsoft.Toolkit.Mvvm.Messaging.Internals
13+
{
14+
/// <summary>
15+
/// A wrapper for <see cref="ConditionalWeakTable{TKey,TValue}"/>
16+
/// that backports the enumerable support to .NET Standard 2.0 through an auxiliary list.
17+
/// </summary>
18+
/// <typeparam name="TKey">Tke key of items to store in the table.</typeparam>
19+
/// <typeparam name="TValue">The values to store in the table.</typeparam>
20+
internal sealed class ConditionalWeakTable2<TKey, TValue>
21+
where TKey : class
22+
where TValue : class?
23+
{
24+
/// <summary>
25+
/// The underlying <see cref="ConditionalWeakTable{TKey,TValue}"/> instance.
26+
/// </summary>
27+
private readonly ConditionalWeakTable<TKey, TValue> table = new();
28+
29+
/// <summary>
30+
/// A supporting linked list to store keys in <see cref="table"/>. This is needed to expose
31+
/// the ability to enumerate existing keys when there is no support for that in the BCL.
32+
/// </summary>
33+
private readonly LinkedList<WeakReference<TKey>> keys = new();
34+
35+
/// <inheritdoc cref="ConditionalWeakTable{TKey,TValue}.TryGetValue"/>
36+
public bool TryGetValue(TKey key, out TValue? value)
37+
{
38+
return this.table.TryGetValue(key, out value);
39+
}
40+
41+
/// <inheritdoc cref="ConditionalWeakTable{TKey,TValue}.GetValue"/>
42+
public TValue GetValue(TKey key, ConditionalWeakTable<TKey, TValue>.CreateValueCallback createValueCallback)
43+
{
44+
// Get or create the value. When this method returns, the key will be present in the table
45+
TValue value = this.table.GetValue(key, createValueCallback);
46+
47+
// Check if the list of keys contains the given key.
48+
// If it does, we can just stop here and return the result.
49+
foreach (WeakReference<TKey> node in this.keys)
50+
{
51+
if (node.TryGetTarget(out TKey? target) &&
52+
ReferenceEquals(target, key))
53+
{
54+
return value;
55+
}
56+
}
57+
58+
// Add the key to the list of weak references to track it
59+
this.keys.AddFirst(new WeakReference<TKey>(key));
60+
61+
return value;
62+
}
63+
64+
/// <inheritdoc cref="ConditionalWeakTable{TKey,TValue}.Remove"/>
65+
public bool Remove(TKey key)
66+
{
67+
return this.table.Remove(key);
68+
}
69+
70+
/// <inheritdoc cref="IEnumerable{T}.GetEnumerator"/>
71+
[Pure]
72+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
73+
public Enumerator GetEnumerator() => new(this);
74+
75+
/// <summary>
76+
/// A custom enumerator that traverses items in a <see cref="ConditionalWeakTable{TKey, TValue}"/> instance.
77+
/// </summary>
78+
public ref struct Enumerator
79+
{
80+
/// <summary>
81+
/// The owner <see cref="ConditionalWeakTable2{TKey, TValue}"/> instance for the enumerator.
82+
/// </summary>
83+
private readonly ConditionalWeakTable2<TKey, TValue> owner;
84+
85+
/// <summary>
86+
/// The current <see cref="LinkedListNode{T}"/>, if any.
87+
/// </summary>
88+
private LinkedListNode<WeakReference<TKey>>? node;
89+
90+
/// <summary>
91+
/// The current <see cref="KeyValuePair{TKey, TValue}"/> to return.
92+
/// </summary>
93+
private KeyValuePair<TKey, TValue> current;
94+
95+
/// <summary>
96+
/// Indicates whether or not <see cref="MoveNext"/> has been called at least once.
97+
/// </summary>
98+
private bool isFirstMoveNextPending;
99+
100+
/// <summary>
101+
/// Initializes a new instance of the <see cref="Enumerator"/> struct.
102+
/// </summary>
103+
/// <param name="owner">The owner <see cref="ConditionalWeakTable2{TKey, TValue}"/> instance for the enumerator.</param>
104+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
105+
public Enumerator(ConditionalWeakTable2<TKey, TValue> owner)
106+
{
107+
this.owner = owner;
108+
this.node = null;
109+
this.current = default;
110+
this.isFirstMoveNextPending = true;
111+
}
112+
113+
/// <inheritdoc cref="System.Collections.IEnumerator.MoveNext"/>
114+
public bool MoveNext()
115+
{
116+
LinkedListNode<WeakReference<TKey>>? node;
117+
118+
if (!isFirstMoveNextPending)
119+
{
120+
node = this.node!.Next;
121+
}
122+
else
123+
{
124+
node = this.owner.keys.First;
125+
126+
this.isFirstMoveNextPending = false;
127+
}
128+
129+
while (node is not null)
130+
{
131+
// Get the key and value for the current node
132+
if (node.Value.TryGetTarget(out TKey? target) &&
133+
this.owner.table.TryGetValue(target!, out TValue? value))
134+
{
135+
this.node = node;
136+
this.current = new KeyValuePair<TKey, TValue>(target, value);
137+
138+
return true;
139+
}
140+
else
141+
{
142+
// If the current key has been collected, trim the list
143+
this.owner.keys.Remove(node);
144+
}
145+
146+
node = node.Next;
147+
}
148+
149+
return false;
150+
}
151+
152+
/// <inheritdoc cref="System.Collections.IEnumerator.MoveNext"/>
153+
public readonly KeyValuePair<TKey, TValue> Current
154+
{
155+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
156+
get => this.current;
157+
}
158+
}
159+
}
160+
}
161+
162+
#endif

Microsoft.Toolkit.Mvvm/Messaging/Internals/System/Gen2GcCallback.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
34

45
using System.Runtime.ConstrainedExecution;
56
using System.Runtime.InteropServices;

0 commit comments

Comments
 (0)