Skip to content

Commit ae07326

Browse files
Merge pull request #4050 from Sergio0694/feature/weak-messenger-callback
WeakReferenceMessenger automatic cleanup
2 parents 9e6708f + 150ccf4 commit ae07326

File tree

13 files changed

+542
-313
lines changed

13 files changed

+542
-313
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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+
namespace System.Diagnostics.CodeAnalysis
8+
{
9+
/// <summary>
10+
/// Specifies that when a method returns <see cref="ReturnValue"/>, the parameter will not be null even if the corresponding type allows it.
11+
/// </summary>
12+
/// <remarks>Internal copy from the BCL attribute.</remarks>
13+
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
14+
internal sealed class NotNullWhenAttribute : Attribute
15+
{
16+
/// <summary>
17+
/// Initializes a new instance of the <see cref="NotNullWhenAttribute"/> class.
18+
/// </summary>
19+
/// <param name="returnValue">The return value condition. If the method returns this value, the associated parameter will not be null.</param>
20+
public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;
21+
22+
/// <summary>
23+
/// Gets a value indicating whether the annotated variable is not <see langword="null"/>.
24+
/// </summary>
25+
public bool ReturnValue { get; }
26+
}
27+
}
28+
29+
#endif

Microsoft.Toolkit.Mvvm/ComponentModel/ObservableRecipient.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ protected virtual void Broadcast<T>(T oldValue, T newValue, string? propertyName
118118
{
119119
PropertyChangedMessage<T> message = new(this, propertyName, oldValue, newValue);
120120

121-
Messenger.Send(message);
121+
_ = Messenger.Send(message);
122122
}
123123

124124
/// <summary>

Microsoft.Toolkit.Mvvm/ComponentModel/ObservableValidator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -727,7 +727,7 @@ static Dictionary<string, string> GetDisplayNames(Type type)
727727

728728
// This method replicates the logic of DisplayName and GetDisplayName from the
729729
// ValidationContext class. See the original source in the BCL for more details.
730-
DisplayNamesMap.GetValue(GetType(), static t => GetDisplayNames(t)).TryGetValue(propertyName, out string? displayName);
730+
_ = DisplayNamesMap.GetValue(GetType(), static t => GetDisplayNames(t)).TryGetValue(propertyName, out string? displayName);
731731

732732
return displayName ?? propertyName;
733733
}

Microsoft.Toolkit.Mvvm/Input/AsyncRelayCommand.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ public bool CanExecute(object? parameter)
146146
/// <inheritdoc/>
147147
public void Execute(object? parameter)
148148
{
149-
ExecuteAsync(parameter);
149+
_ = ExecuteAsync(parameter);
150150
}
151151

152152
/// <inheritdoc/>

Microsoft.Toolkit.Mvvm/Input/AsyncRelayCommand{T}.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,13 +144,13 @@ public bool CanExecute(object? parameter)
144144
[MethodImpl(MethodImplOptions.AggressiveInlining)]
145145
public void Execute(T? parameter)
146146
{
147-
ExecuteAsync(parameter);
147+
_ = ExecuteAsync(parameter);
148148
}
149149

150150
/// <inheritdoc/>
151151
public void Execute(object? parameter)
152152
{
153-
ExecuteAsync((T?)parameter);
153+
_ = ExecuteAsync((T?)parameter);
154154
}
155155

156156
/// <inheritdoc/>
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: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
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.CodeAnalysis;
10+
using System.Diagnostics.Contracts;
11+
using System.Runtime.CompilerServices;
12+
13+
namespace Microsoft.Toolkit.Mvvm.Messaging.Internals
14+
{
15+
/// <summary>
16+
/// A wrapper for <see cref="ConditionalWeakTable{TKey,TValue}"/>
17+
/// that backports the enumerable support to .NET Standard 2.0 through an auxiliary list.
18+
/// </summary>
19+
/// <typeparam name="TKey">Tke key of items to store in the table.</typeparam>
20+
/// <typeparam name="TValue">The values to store in the table.</typeparam>
21+
internal sealed class ConditionalWeakTable2<TKey, TValue>
22+
where TKey : class
23+
where TValue : class?
24+
{
25+
/// <summary>
26+
/// The underlying <see cref="ConditionalWeakTable{TKey,TValue}"/> instance.
27+
/// </summary>
28+
private readonly ConditionalWeakTable<TKey, TValue> table = new();
29+
30+
/// <summary>
31+
/// A supporting linked list to store keys in <see cref="table"/>. This is needed to expose
32+
/// the ability to enumerate existing keys when there is no support for that in the BCL.
33+
/// </summary>
34+
private readonly LinkedList<WeakReference<TKey>> keys = new();
35+
36+
/// <inheritdoc cref="ConditionalWeakTable{TKey,TValue}.TryGetValue"/>
37+
public bool TryGetValue(TKey key, [NotNullWhen(true)] out TValue? value)
38+
{
39+
return this.table.TryGetValue(key, out value);
40+
}
41+
42+
/// <inheritdoc cref="ConditionalWeakTable{TKey,TValue}.GetValue"/>
43+
public TValue GetValue(TKey key, ConditionalWeakTable<TKey, TValue>.CreateValueCallback createValueCallback)
44+
{
45+
// Get or create the value. When this method returns, the key will be present in the table
46+
TValue value = this.table.GetValue(key, createValueCallback);
47+
48+
// Check if the list of keys contains the given key.
49+
// If it does, we can just stop here and return the result.
50+
foreach (WeakReference<TKey> node in this.keys)
51+
{
52+
if (node.TryGetTarget(out TKey? target) &&
53+
ReferenceEquals(target, key))
54+
{
55+
return value;
56+
}
57+
}
58+
59+
// Add the key to the list of weak references to track it
60+
this.keys.AddFirst(new WeakReference<TKey>(key));
61+
62+
return value;
63+
}
64+
65+
/// <inheritdoc cref="ConditionalWeakTable{TKey,TValue}.Remove"/>
66+
public bool Remove(TKey key)
67+
{
68+
return this.table.Remove(key);
69+
}
70+
71+
/// <inheritdoc cref="IEnumerable{T}.GetEnumerator"/>
72+
[Pure]
73+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
74+
public Enumerator GetEnumerator() => new(this);
75+
76+
/// <summary>
77+
/// A custom enumerator that traverses items in a <see cref="ConditionalWeakTable{TKey, TValue}"/> instance.
78+
/// </summary>
79+
public ref struct Enumerator
80+
{
81+
/// <summary>
82+
/// The owner <see cref="ConditionalWeakTable2{TKey, TValue}"/> instance for the enumerator.
83+
/// </summary>
84+
private readonly ConditionalWeakTable2<TKey, TValue> owner;
85+
86+
/// <summary>
87+
/// The current <see cref="LinkedListNode{T}"/>, if any.
88+
/// </summary>
89+
private LinkedListNode<WeakReference<TKey>>? node;
90+
91+
/// <summary>
92+
/// The current <see cref="KeyValuePair{TKey, TValue}"/> to return.
93+
/// </summary>
94+
private KeyValuePair<TKey, TValue> current;
95+
96+
/// <summary>
97+
/// Indicates whether or not <see cref="MoveNext"/> has been called at least once.
98+
/// </summary>
99+
private bool isFirstMoveNextPending;
100+
101+
/// <summary>
102+
/// Initializes a new instance of the <see cref="Enumerator"/> struct.
103+
/// </summary>
104+
/// <param name="owner">The owner <see cref="ConditionalWeakTable2{TKey, TValue}"/> instance for the enumerator.</param>
105+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
106+
public Enumerator(ConditionalWeakTable2<TKey, TValue> owner)
107+
{
108+
this.owner = owner;
109+
this.node = null;
110+
this.current = default;
111+
this.isFirstMoveNextPending = true;
112+
}
113+
114+
/// <inheritdoc cref="System.Collections.IEnumerator.MoveNext"/>
115+
public bool MoveNext()
116+
{
117+
LinkedListNode<WeakReference<TKey>>? node;
118+
119+
if (!isFirstMoveNextPending)
120+
{
121+
node = this.node!.Next;
122+
}
123+
else
124+
{
125+
node = this.owner.keys.First;
126+
127+
this.isFirstMoveNextPending = false;
128+
}
129+
130+
while (node is not null)
131+
{
132+
LinkedListNode<WeakReference<TKey>>? nextNode = node.Next;
133+
134+
// Get the key and value for the current node
135+
if (node.Value.TryGetTarget(out TKey? target) &&
136+
this.owner.table.TryGetValue(target!, out TValue? value))
137+
{
138+
this.node = node;
139+
this.current = new KeyValuePair<TKey, TValue>(target, value);
140+
141+
return true;
142+
}
143+
else
144+
{
145+
// If the current key has been collected, trim the list
146+
this.owner.keys.Remove(node);
147+
}
148+
149+
node = nextNode;
150+
}
151+
152+
return false;
153+
}
154+
155+
/// <inheritdoc cref="System.Collections.IEnumerator.MoveNext"/>
156+
public readonly KeyValuePair<TKey, TValue> Current
157+
{
158+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
159+
get => this.current;
160+
}
161+
}
162+
}
163+
}
164+
165+
#endif

0 commit comments

Comments
 (0)