Skip to content

Commit 4855fec

Browse files
committed
Merge branch 'master' into dev/7.0.0
2 parents 971cf01 + 5004edb commit 4855fec

File tree

369 files changed

+18406
-2970
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

369 files changed

+18406
-2970
lines changed

.editorconfig

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,11 @@ dotnet_naming_style.prefix_private_field_with_underscore.capitalization
237237
dotnet_naming_style.prefix_private_field_with_underscore.required_prefix = _
238238

239239
# Code files
240+
241+
# SA1009: Closing parenthesis should be spaced correctly
242+
# Needed for null forgiving operator after functions, "foo()!"
243+
dotnet_diagnostic.SA1009.severity = none
244+
240245
[*.{cs,vb}]
241246

242247
# Migrate back from old Toolkit.ruleset

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ Please check if your PR fulfills the following requirements:
3434
- [ ] Icon has been created (if new sample) following the [Thumbnail Style Guide and templates](https://github.com/windows-toolkit/WindowsCommunityToolkit-design-assets)
3535
- [ ] Tests for the changes have been added (for bug fixes / features) (if applicable)
3636
- [ ] Header has been added to all new source files (run *build/UpdateHeaders.bat*)
37-
- [ ] If new feature, add to [Feature List](https://github.com/windows-toolkit/WindowsCommunityToolkit/blob/master/readme.md#-features)
3837
- [ ] Contains **NO** breaking changes
3938

4039
<!-- If this PR contains a breaking change, please describe the impact and migration path for existing applications below.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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_1_OR_GREATER
6+
7+
namespace System.Diagnostics.CodeAnalysis
8+
{
9+
/// <summary>
10+
/// Specifies that when a method returns <see cref="ReturnValue"/>, the parameter
11+
/// will not be null even if the corresponding type allows it.
12+
/// </summary>
13+
/// <remarks>Internal copy of the .NET Standard 2.1 attribute.</remarks>
14+
[AttributeUsage(AttributeTargets.Parameter)]
15+
internal sealed class NotNullWhenAttribute : Attribute
16+
{
17+
/// <summary>
18+
/// Initializes a new instance of the <see cref="NotNullWhenAttribute"/> class.
19+
/// </summary>
20+
/// <param name="returnValue">
21+
/// The return value condition. If the method returns this value,
22+
/// the associated parameter will not be <see langword="null"/>.
23+
/// </param>
24+
public NotNullWhenAttribute(bool returnValue)
25+
{
26+
ReturnValue = returnValue;
27+
}
28+
29+
/// <summary>
30+
/// Gets a value indicating whether the return value should be <see langword="true"/>.
31+
/// </summary>
32+
public bool ReturnValue { get; }
33+
}
34+
}
35+
36+
#endif

Microsoft.Toolkit.HighPerformance/Box{T}.cs

Lines changed: 68 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,31 @@
88
using System.Diagnostics.Contracts;
99
using System.Runtime.CompilerServices;
1010

11-
#nullable enable
12-
1311
namespace Microsoft.Toolkit.HighPerformance
1412
{
1513
/// <summary>
1614
/// A <see langword="class"/> that represents a boxed <typeparamref name="T"/> value on the managed heap.
15+
/// This is a "shadow" type that can be used in place of a non-generic <see cref="object"/> reference to a
16+
/// boxed value type, to make the code more expressive and reduce the chances of errors.
17+
/// Consider this example:
18+
/// <code>
19+
/// object obj = 42;
20+
///
21+
/// // Manual, error prone unboxing
22+
/// int sum = (int)obj + 1;
23+
/// </code>
24+
/// In this example, it is not possible to know in advance what type is actually being boxed in a given
25+
/// <see cref="object"/> instance, making the code less robust at build time. The <see cref="Box{T}"/>
26+
/// type can be used as a drop-in replacement in this case, like so:
27+
/// <code>
28+
/// Box&lt;int> box = 42;
29+
///
30+
/// // Build-time validation, automatic unboxing
31+
/// int sum = box.Value + 1;
32+
/// </code>
33+
/// This type can also be useful when dealing with large custom value types that are also boxed, as
34+
/// it allows to retrieve a mutable reference to the boxing value. This means that a given boxed
35+
/// value can be mutated in-place, instead of having to allocate a new updated boxed instance.
1736
/// </summary>
1837
/// <typeparam name="T">The type of value being boxed.</typeparam>
1938
[DebuggerDisplay("{ToString(),raw}")]
@@ -39,8 +58,10 @@ public sealed class Box<T>
3958
/// This constructor is never used, it is only declared in order to mark it with
4059
/// the <see langword="private"/> visibility modifier and prevent direct use.
4160
/// </remarks>
61+
/// <exception cref="InvalidOperationException">Always thrown when this constructor is used (eg. from reflection).</exception>
4262
private Box()
4363
{
64+
throw new InvalidOperationException("The Microsoft.Toolkit.HighPerformance.Box<T> constructor should never be used");
4465
}
4566

4667
/// <summary>
@@ -83,18 +104,7 @@ public static Box<T> DangerousGetFrom(object obj)
83104
/// <param name="box">The resulting <see cref="Box{T}"/> reference, if <paramref name="obj"/> was a boxed <typeparamref name="T"/> value.</param>
84105
/// <returns><see langword="true"/> if a <see cref="Box{T}"/> instance was retrieved correctly, <see langword="false"/> otherwise.</returns>
85106
[MethodImpl(MethodImplOptions.AggressiveInlining)]
86-
[SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1115", Justification = "Comment for [NotNullWhen] attribute")]
87-
public static bool TryGetFrom(
88-
object obj,
89-
#if NETSTANDARD2_1
90-
/* On .NET Standard 2.1, we can add the [NotNullWhen] attribute
91-
* to let the code analysis engine know that whenever this method
92-
* returns true, box will always be assigned to a non-null value.
93-
* This will eliminate the null warnings when in a branch that
94-
* is only taken when this method returns true. */
95-
[NotNullWhen(true)]
96-
#endif
97-
out Box<T>? box)
107+
public static bool TryGetFrom(object obj, [NotNullWhen(true)] out Box<T>? box)
98108
{
99109
if (obj.GetType() == typeof(T))
100110
{
@@ -125,38 +135,38 @@ public static implicit operator T(Box<T> box)
125135
[MethodImpl(MethodImplOptions.AggressiveInlining)]
126136
public static implicit operator Box<T>(T value)
127137
{
128-
/* The Box<T> type is never actually instantiated.
129-
* Here we are just boxing the input T value, and then reinterpreting
130-
* that object reference as a Box<T> reference. As such, the Box<T>
131-
* type is really only used as an interface to access the contents
132-
* of a boxed value type. This also makes it so that additional methods
133-
* like ToString() or GetHashCode() will automatically be referenced from
134-
* the method table of the boxed object, meaning that they don't need to
135-
* manually be implemented in the Box<T> type. For instance, boxing a float
136-
* and calling ToString() on it directly, on its boxed object or on a Box<T>
137-
* reference retrieved from it will produce the same result in all cases. */
138+
// The Box<T> type is never actually instantiated.
139+
// Here we are just boxing the input T value, and then reinterpreting
140+
// that object reference as a Box<T> reference. As such, the Box<T>
141+
// type is really only used as an interface to access the contents
142+
// of a boxed value type. This also makes it so that additional methods
143+
// like ToString() or GetHashCode() will automatically be referenced from
144+
// the method table of the boxed object, meaning that they don't need to
145+
// manually be implemented in the Box<T> type. For instance, boxing a float
146+
// and calling ToString() on it directly, on its boxed object or on a Box<T>
147+
// reference retrieved from it will produce the same result in all cases.
138148
return Unsafe.As<Box<T>>(value);
139149
}
140150

141151
/// <inheritdoc/>
142152
public override string ToString()
143153
{
144-
/* Here we're overriding the base object virtual methods to ensure
145-
* calls to those methods have a correct results on all runtimes.
146-
* For instance, not doing so is causing issue on .NET Core 2.1 Release
147-
* due to how the runtime handles the Box<T> reference to an actual
148-
* boxed T value (not a concrete Box<T> instance as it would expect).
149-
* To fix that, the overrides will simply call the expected methods
150-
* directly on the boxed T values. These methods will be directly
151-
* invoked by the JIT compiler when using a Box<T> reference. When
152-
* an object reference is used instead, the call would be forwarded
153-
* to those same methods anyway, since the method table for an object
154-
* representing a T instance is the one of type T anyway. */
155-
return this.GetReference().ToString();
154+
// Here we're overriding the base object virtual methods to ensure
155+
// calls to those methods have a correct results on all runtimes.
156+
// For instance, not doing so is causing issue on .NET Core 2.1 Release
157+
// due to how the runtime handles the Box<T> reference to an actual
158+
// boxed T value (not a concrete Box<T> instance as it would expect).
159+
// To fix that, the overrides will simply call the expected methods
160+
// directly on the boxed T values. These methods will be directly
161+
// invoked by the JIT compiler when using a Box<T> reference. When
162+
// an object reference is used instead, the call would be forwarded
163+
// to those same methods anyway, since the method table for an object
164+
// representing a T instance is the one of type T anyway.
165+
return this.GetReference().ToString()!;
156166
}
157167

158168
/// <inheritdoc/>
159-
public override bool Equals(object obj)
169+
public override bool Equals(object? obj)
160170
{
161171
return Equals(this, obj);
162172
}
@@ -177,11 +187,12 @@ private static void ThrowInvalidCastExceptionForGetFrom()
177187
}
178188
}
179189

190+
#pragma warning disable SA1402 // Extensions being declared after the type they apply to
191+
#pragma warning disable SA1204 // Extension class to replace instance methods for Box<T>
192+
180193
/// <summary>
181194
/// Helpers for working with the <see cref="Box{T}"/> type.
182195
/// </summary>
183-
[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402", Justification = "Extension class to replace instance methods for Box<T>")]
184-
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1204", Justification = "Extensions being declared after the type they apply to")]
185196
public static class BoxExtensions
186197
{
187198
/// <summary>
@@ -195,24 +206,24 @@ public static class BoxExtensions
195206
public static ref T GetReference<T>(this Box<T> box)
196207
where T : struct
197208
{
198-
/* The reason why this method is an extension and is not part of
199-
* the Box<T> type itself is that Box<T> is really just a mask
200-
* used over object references, but it is never actually instantiated.
201-
* Because of this, the method table of the objects in the heap will
202-
* be the one of type T created by the runtime, and not the one of
203-
* the Box<T> type. To avoid potential issues when invoking this method
204-
* on different runtimes, which might handle that scenario differently,
205-
* we use an extension method, which is just syntactic sugar for a static
206-
* method belonging to another class. This isn't technically necessary,
207-
* but it's just an extra precaution since the syntax for users remains
208-
* exactly the same anyway. Here we just call the Unsafe.Unbox<T>(object)
209-
* API, which is hidden away for users of the type for simplicity.
210-
* Note that this API will always actually involve a conditional
211-
* branch, which is introduced by the JIT compiler to validate the
212-
* object instance being unboxed. But since the alternative of
213-
* manually tracking the offset to the boxed data would be both
214-
* more error prone, and it would still introduce some overhead,
215-
* this doesn't really matter in this case anyway. */
209+
// The reason why this method is an extension and is not part of
210+
// the Box<T> type itself is that Box<T> is really just a mask
211+
// used over object references, but it is never actually instantiated.
212+
// Because of this, the method table of the objects in the heap will
213+
// be the one of type T created by the runtime, and not the one of
214+
// the Box<T> type. To avoid potential issues when invoking this method
215+
// on different runtimes, which might handle that scenario differently,
216+
// we use an extension method, which is just syntactic sugar for a static
217+
// method belonging to another class. This isn't technically necessary,
218+
// but it's just an extra precaution since the syntax for users remains
219+
// exactly the same anyway. Here we just call the Unsafe.Unbox<T>(object)
220+
// API, which is hidden away for users of the type for simplicity.
221+
// Note that this API will always actually involve a conditional
222+
// branch, which is introduced by the JIT compiler to validate the
223+
// object instance being unboxed. But since the alternative of
224+
// manually tracking the offset to the boxed data would be both
225+
// more error prone, and it would still introduce some overhead,
226+
// this doesn't really matter in this case anyway.
216227
return ref Unsafe.Unbox<T>(box);
217228
}
218229
}

Microsoft.Toolkit.HighPerformance/Buffers/ArrayPoolBufferWriter{T}.cs

Lines changed: 21 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,12 @@
55
using System;
66
using System.Buffers;
77
using System.Diagnostics;
8-
using System.Diagnostics.CodeAnalysis;
98
using System.Diagnostics.Contracts;
109
using System.Runtime.CompilerServices;
1110
using System.Runtime.InteropServices;
1211
using Microsoft.Toolkit.HighPerformance.Buffers.Views;
1312
using Microsoft.Toolkit.HighPerformance.Extensions;
1413

15-
#nullable enable
16-
1714
namespace Microsoft.Toolkit.HighPerformance.Buffers
1815
{
1916
/// <summary>
@@ -29,7 +26,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
2926
/// </remarks>
3027
[DebuggerTypeProxy(typeof(ArrayPoolBufferWriterDebugView<>))]
3128
[DebuggerDisplay("{ToString(),raw}")]
32-
public sealed class ArrayPoolBufferWriter<T> : IBufferWriter<T>, IMemoryOwner<T>
29+
public sealed class ArrayPoolBufferWriter<T> : IBuffer<T>, IMemoryOwner<T>
3330
{
3431
/// <summary>
3532
/// The default buffer size to use to expand empty arrays.
@@ -41,7 +38,7 @@ public sealed class ArrayPoolBufferWriter<T> : IBufferWriter<T>, IMemoryOwner<T>
4138
/// </summary>
4239
private T[]? array;
4340

44-
#pragma warning disable IDE0032
41+
#pragma warning disable IDE0032 // Use field over auto-property (clearer and faster)
4542
/// <summary>
4643
/// The starting offset within <see cref="array"/>.
4744
/// </summary>
@@ -53,9 +50,9 @@ public sealed class ArrayPoolBufferWriter<T> : IBufferWriter<T>, IMemoryOwner<T>
5350
/// </summary>
5451
public ArrayPoolBufferWriter()
5552
{
56-
/* Since we're using pooled arrays, we can rent the buffer with the
57-
* default size immediately, we don't need to use lazy initialization
58-
* to save unnecessary memory allocations in this case. */
53+
// Since we're using pooled arrays, we can rent the buffer with the
54+
// default size immediately, we don't need to use lazy initialization
55+
// to save unnecessary memory allocations in this case.
5956
this.array = ArrayPool<T>.Shared.Rent(DefaultInitialBufferSize);
6057
this.index = 0;
6158
}
@@ -86,22 +83,20 @@ public ArrayPoolBufferWriter(int initialCapacity)
8683
/// <inheritdoc/>
8784
Memory<T> IMemoryOwner<T>.Memory
8885
{
89-
/* This property is explicitly implemented so that it's hidden
90-
* under normal usage, as the name could be confusing when
91-
* displayed besides WrittenMemory and GetMemory().
92-
* The IMemoryOwner<T> interface is implemented primarily
93-
* so that the AsStream() extension can be used on this type,
94-
* allowing users to first create a ArrayPoolBufferWriter<byte>
95-
* instance to write data to, then get a stream through the
96-
* extension and let it take care of returning the underlying
97-
* buffer to the shared pool when it's no longer necessary.
98-
* Inlining is not needed here since this will always be a callvirt. */
86+
// This property is explicitly implemented so that it's hidden
87+
// under normal usage, as the name could be confusing when
88+
// displayed besides WrittenMemory and GetMemory().
89+
// The IMemoryOwner<T> interface is implemented primarily
90+
// so that the AsStream() extension can be used on this type,
91+
// allowing users to first create a ArrayPoolBufferWriter<byte>
92+
// instance to write data to, then get a stream through the
93+
// extension and let it take care of returning the underlying
94+
// buffer to the shared pool when it's no longer necessary.
95+
// Inlining is not needed here since this will always be a callvirt.
9996
get => MemoryMarshal.AsMemory(WrittenMemory);
10097
}
10198

102-
/// <summary>
103-
/// Gets the data written to the underlying buffer so far, as a <see cref="ReadOnlyMemory{T}"/>.
104-
/// </summary>
99+
/// <inheritdoc/>
105100
public ReadOnlyMemory<T> WrittenMemory
106101
{
107102
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -118,9 +113,7 @@ public ReadOnlyMemory<T> WrittenMemory
118113
}
119114
}
120115

121-
/// <summary>
122-
/// Gets the data written to the underlying buffer so far, as a <see cref="ReadOnlySpan{T}"/>.
123-
/// </summary>
116+
/// <inheritdoc/>
124117
public ReadOnlySpan<T> WrittenSpan
125118
{
126119
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -137,18 +130,14 @@ public ReadOnlySpan<T> WrittenSpan
137130
}
138131
}
139132

140-
/// <summary>
141-
/// Gets the amount of data written to the underlying buffer so far.
142-
/// </summary>
133+
/// <inheritdoc/>
143134
public int WrittenCount
144135
{
145136
[MethodImpl(MethodImplOptions.AggressiveInlining)]
146137
get => this.index;
147138
}
148139

149-
/// <summary>
150-
/// Gets the total amount of space within the underlying buffer.
151-
/// </summary>
140+
/// <inheritdoc/>
152141
public int Capacity
153142
{
154143
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -165,9 +154,7 @@ public int Capacity
165154
}
166155
}
167156

168-
/// <summary>
169-
/// Gets the amount of space available that can still be written into without forcing the underlying buffer to grow.
170-
/// </summary>
157+
/// <inheritdoc/>
171158
public int FreeCapacity
172159
{
173160
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -184,12 +171,7 @@ public int FreeCapacity
184171
}
185172
}
186173

187-
/// <summary>
188-
/// Clears the data written to the underlying buffer.
189-
/// </summary>
190-
/// <remarks>
191-
/// You must clear the <see cref="ArrayPoolBufferWriter{T}"/> before trying to re-use it.
192-
/// </remarks>
174+
/// <inheritdoc/>
193175
public void Clear()
194176
{
195177
T[]? array = this.array;
@@ -308,7 +290,6 @@ public override string ToString()
308290
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the initial capacity is invalid.
309291
/// </summary>
310292
[MethodImpl(MethodImplOptions.NoInlining)]
311-
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1204", Justification = "Exception throwers at the end of class")]
312293
private static void ThrowArgumentOutOfRangeExceptionForInitialCapacity()
313294
{
314295
throw new ArgumentOutOfRangeException("initialCapacity", "The initial capacity must be a positive value");

0 commit comments

Comments
 (0)