Skip to content
29 changes: 29 additions & 0 deletions Microsoft.Toolkit.Mvvm/Attributes/NotNullWhenAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#if NETSTANDARD2_0

namespace System.Diagnostics.CodeAnalysis
{
/// <summary>
/// Specifies that when a method returns <see cref="ReturnValue"/>, the parameter will not be null even if the corresponding type allows it.
/// </summary>
/// <remarks>Internal copy from the BCL attribute.</remarks>
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
internal sealed class NotNullWhenAttribute : Attribute
{
/// <summary>
/// Initializes a new instance of the <see cref="NotNullWhenAttribute"/> class.
/// </summary>
/// <param name="returnValue">The return value condition. If the method returns this value, the associated parameter will not be null.</param>
public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;

/// <summary>
/// Gets a value indicating whether the annotated variable is not <see langword="null"/>.
/// </summary>
public bool ReturnValue { get; }
}
}

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ protected virtual void Broadcast<T>(T oldValue, T newValue, string? propertyName
{
PropertyChangedMessage<T> message = new(this, propertyName, oldValue, newValue);

Messenger.Send(message);
_ = Messenger.Send(message);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -727,7 +727,7 @@ static Dictionary<string, string> GetDisplayNames(Type type)

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

return displayName ?? propertyName;
}
Expand Down
2 changes: 1 addition & 1 deletion Microsoft.Toolkit.Mvvm/Input/AsyncRelayCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ public bool CanExecute(object? parameter)
/// <inheritdoc/>
public void Execute(object? parameter)
{
ExecuteAsync(parameter);
_ = ExecuteAsync(parameter);
}

/// <inheritdoc/>
Expand Down
4 changes: 2 additions & 2 deletions Microsoft.Toolkit.Mvvm/Input/AsyncRelayCommand{T}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,13 +144,13 @@ public bool CanExecute(object? parameter)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Execute(T? parameter)
{
ExecuteAsync(parameter);
_ = ExecuteAsync(parameter);
}

/// <inheritdoc/>
public void Execute(object? parameter)
{
ExecuteAsync((T?)parameter);
_ = ExecuteAsync((T?)parameter);
}

/// <inheritdoc/>
Expand Down
107 changes: 107 additions & 0 deletions Microsoft.Toolkit.Mvvm/Messaging/Internals/ArrayPoolBufferWriter{T}.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Buffers;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;

namespace Microsoft.Toolkit.Mvvm.Messaging.Internals
{
/// <summary>
/// A simple buffer writer implementation using pooled arrays.
/// </summary>
/// <typeparam name="T">The type of items to store in the list.</typeparam>
/// <remarks>
/// This type is a <see langword="ref"/> <see langword="struct"/> to avoid the object allocation and to
/// enable the pattern-based <see cref="IDisposable"/> support. We aren't worried with consumers not
/// using this type correctly since it's private and only accessible within the parent type.
/// </remarks>
internal ref struct ArrayPoolBufferWriter<T>
{
/// <summary>
/// The default buffer size to use to expand empty arrays.
/// </summary>
private const int DefaultInitialBufferSize = 128;

/// <summary>
/// The underlying <typeparamref name="T"/> array.
/// </summary>
private T[] array;

/// <summary>
/// The starting offset within <see cref="array"/>.
/// </summary>
private int index;

/// <summary>
/// Creates a new instance of the <see cref="ArrayPoolBufferWriter{T}"/> struct.
/// </summary>
/// <returns>A new <see cref="ArrayPoolBufferWriter{T}"/> instance.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ArrayPoolBufferWriter<T> Create()
{
return new() { array = ArrayPool<T>.Shared.Rent(DefaultInitialBufferSize) };
}

/// <summary>
/// Gets a <see cref="ReadOnlySpan{T}"/> with the current items.
/// </summary>
public ReadOnlySpan<T> Span
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.array.AsSpan(0, this.index);
}

/// <summary>
/// Adds a new item to the current collection.
/// </summary>
/// <param name="item">The item to add.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add(T item)
{
if (this.index == this.array.Length)
{
ResizeBuffer();
}

this.array[this.index++] = item;
}

/// <summary>
/// Resets the underlying array and the stored items.
/// </summary>
public void Reset()
{
Array.Clear(this.array, 0, this.index);

this.index = 0;
}

/// <summary>
/// Resizes <see cref="array"/> when there is no space left for new items.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private void ResizeBuffer()
{
T[] rent = ArrayPool<T>.Shared.Rent(this.index << 2);

Array.Copy(this.array, 0, rent, 0, this.index);
Array.Clear(this.array, 0, this.index);

ArrayPool<T>.Shared.Return(this.array);

this.array = rent;
}

/// <inheritdoc cref="IDisposable.Dispose"/>
public void Dispose()
{
Array.Clear(this.array, 0, this.index);

ArrayPool<T>.Shared.Return(this.array);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#if NETSTANDARD2_0

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;

namespace Microsoft.Toolkit.Mvvm.Messaging.Internals
{
/// <summary>
/// A wrapper for <see cref="ConditionalWeakTable{TKey,TValue}"/>
/// that backports the enumerable support to .NET Standard 2.0 through an auxiliary list.
/// </summary>
/// <typeparam name="TKey">Tke key of items to store in the table.</typeparam>
/// <typeparam name="TValue">The values to store in the table.</typeparam>
internal sealed class ConditionalWeakTable2<TKey, TValue>
where TKey : class
where TValue : class?
{
/// <summary>
/// The underlying <see cref="ConditionalWeakTable{TKey,TValue}"/> instance.
/// </summary>
private readonly ConditionalWeakTable<TKey, TValue> table = new();

/// <summary>
/// A supporting linked list to store keys in <see cref="table"/>. This is needed to expose
/// the ability to enumerate existing keys when there is no support for that in the BCL.
/// </summary>
private readonly LinkedList<WeakReference<TKey>> keys = new();

/// <inheritdoc cref="ConditionalWeakTable{TKey,TValue}.TryGetValue"/>
public bool TryGetValue(TKey key, [NotNullWhen(true)] out TValue? value)
{
return this.table.TryGetValue(key, out value);
}

/// <inheritdoc cref="ConditionalWeakTable{TKey,TValue}.GetValue"/>
public TValue GetValue(TKey key, ConditionalWeakTable<TKey, TValue>.CreateValueCallback createValueCallback)
{
// Get or create the value. When this method returns, the key will be present in the table
TValue value = this.table.GetValue(key, createValueCallback);

// Check if the list of keys contains the given key.
// If it does, we can just stop here and return the result.
foreach (WeakReference<TKey> node in this.keys)
{
if (node.TryGetTarget(out TKey? target) &&
ReferenceEquals(target, key))
{
return value;
}
}

// Add the key to the list of weak references to track it
this.keys.AddFirst(new WeakReference<TKey>(key));

return value;
}

/// <inheritdoc cref="ConditionalWeakTable{TKey,TValue}.Remove"/>
public bool Remove(TKey key)
{
return this.table.Remove(key);
}

/// <inheritdoc cref="IEnumerable{T}.GetEnumerator"/>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Enumerator GetEnumerator() => new(this);

/// <summary>
/// A custom enumerator that traverses items in a <see cref="ConditionalWeakTable{TKey, TValue}"/> instance.
/// </summary>
public ref struct Enumerator
{
/// <summary>
/// The owner <see cref="ConditionalWeakTable2{TKey, TValue}"/> instance for the enumerator.
/// </summary>
private readonly ConditionalWeakTable2<TKey, TValue> owner;

/// <summary>
/// The current <see cref="LinkedListNode{T}"/>, if any.
/// </summary>
private LinkedListNode<WeakReference<TKey>>? node;

/// <summary>
/// The current <see cref="KeyValuePair{TKey, TValue}"/> to return.
/// </summary>
private KeyValuePair<TKey, TValue> current;

/// <summary>
/// Indicates whether or not <see cref="MoveNext"/> has been called at least once.
/// </summary>
private bool isFirstMoveNextPending;

/// <summary>
/// Initializes a new instance of the <see cref="Enumerator"/> struct.
/// </summary>
/// <param name="owner">The owner <see cref="ConditionalWeakTable2{TKey, TValue}"/> instance for the enumerator.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Enumerator(ConditionalWeakTable2<TKey, TValue> owner)
{
this.owner = owner;
this.node = null;
this.current = default;
this.isFirstMoveNextPending = true;
}

/// <inheritdoc cref="System.Collections.IEnumerator.MoveNext"/>
public bool MoveNext()
{
LinkedListNode<WeakReference<TKey>>? node;

if (!isFirstMoveNextPending)
{
node = this.node!.Next;
}
else
{
node = this.owner.keys.First;

this.isFirstMoveNextPending = false;
}

while (node is not null)
{
LinkedListNode<WeakReference<TKey>>? nextNode = node.Next;

// Get the key and value for the current node
if (node.Value.TryGetTarget(out TKey? target) &&
this.owner.table.TryGetValue(target!, out TValue? value))
{
this.node = node;
this.current = new KeyValuePair<TKey, TValue>(target, value);

return true;
}
else
{
// If the current key has been collected, trim the list
this.owner.keys.Remove(node);
}

node = nextNode;
}

return false;
}

/// <inheritdoc cref="System.Collections.IEnumerator.MoveNext"/>
public readonly KeyValuePair<TKey, TValue> Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.current;
}
}
}
}

#endif
Loading