Summary
Assert.HasCount<T> today only accepts IEnumerable<T>. With Span<T>/ReadOnlySpan<T>/Memory<T>/ReadOnlyMemory<T> being pervasive in modern .NET (System.Buffers, pipelines, parsers, low-allocation APIs), users repeatedly hit a gap right next to sites that MSTEST0037 happily rewrites.
Repro
In dotnet/sdk PR #54766, turning on MSTestAnalysisMode=Recommended flagged ~25 length/count assertions in ArrayBufferWriterTests.cs. All the IEnumerable/ArraySegment/array cases were auto-fixed to Assert.HasCount. But sites like:
csharp Assert.AreEqual(written.Length, transientSpan.Length);
cannot be fixed today because there's no HasCount overload that accepts a Span<T> or ReadOnlySpan<T>. The analyzer correctly skips them, but the result is an inconsistent UX: identical-looking call sites get different treatment based on whether the value is an IEnumerable or a Span.
Proposal
-
Add overloads to Assert.Count.cs (and their interpolated-message variants):
csharp public static void HasCount<T>(int expected, ReadOnlySpan<T> span); public static void HasCount<T>(int expected, Span<T> span); public static void HasCount<T>(int expected, ReadOnlyMemory<T> memory); public static void HasCount<T>(int expected, Memory<T> memory);
Span/ReadOnlySpan are ref structs so they cannot satisfy a generic IEnumerable<T> constraint — they need explicit overloads. Memory<T> could technically go through .ToArray() but a direct overload avoids the allocation.
-
Extend MSTEST0037 to flag Assert.AreEqual(x, span.Length) / Assert.AreEqual(x, memory.Length) and suggest Assert.HasCount(x, span) / Assert.HasCount(x, memory) once the overloads exist.
Naming consideration
HasCount reads slightly awkward for Span (.Length, not .Count). HasLength would fit better semantically, but I think API consistency wins — having one assertion name for "this sequence-like thing has N elements" is more discoverable than splitting by underlying property name. Happy to discuss.
Trade-offs
- Surface area grows by 4 overloads × interpolated-string variants. Cost is small and the family precedent (
Assert.AreSequenceEqual already accepts ReadOnlySpan<T>) supports it.
ref struct parameters mean these can't live behind a single generic, but they don't need to — explicit overloads are the standard pattern.
Related
Summary
Assert.HasCount<T>today only acceptsIEnumerable<T>. WithSpan<T>/ReadOnlySpan<T>/Memory<T>/ReadOnlyMemory<T>being pervasive in modern .NET (System.Buffers, pipelines, parsers, low-allocation APIs), users repeatedly hit a gap right next to sites that MSTEST0037 happily rewrites.Repro
In dotnet/sdk PR #54766, turning on
MSTestAnalysisMode=Recommendedflagged ~25 length/count assertions inArrayBufferWriterTests.cs. All theIEnumerable/ArraySegment/array cases were auto-fixed toAssert.HasCount. But sites like:csharp Assert.AreEqual(written.Length, transientSpan.Length);cannot be fixed today because there's no
HasCountoverload that accepts aSpan<T>orReadOnlySpan<T>. The analyzer correctly skips them, but the result is an inconsistent UX: identical-looking call sites get different treatment based on whether the value is anIEnumerableor aSpan.Proposal
Add overloads to
Assert.Count.cs(and their interpolated-message variants):csharp public static void HasCount<T>(int expected, ReadOnlySpan<T> span); public static void HasCount<T>(int expected, Span<T> span); public static void HasCount<T>(int expected, ReadOnlyMemory<T> memory); public static void HasCount<T>(int expected, Memory<T> memory);Span/ReadOnlySpanareref structs so they cannot satisfy a genericIEnumerable<T>constraint — they need explicit overloads.Memory<T>could technically go through.ToArray()but a direct overload avoids the allocation.Extend MSTEST0037 to flag
Assert.AreEqual(x, span.Length)/Assert.AreEqual(x, memory.Length)and suggestAssert.HasCount(x, span)/Assert.HasCount(x, memory)once the overloads exist.Naming consideration
HasCountreads slightly awkward forSpan(.Length, not.Count).HasLengthwould fit better semantically, but I think API consistency wins — having one assertion name for "this sequence-like thing has N elements" is more discoverable than splitting by underlying property name. Happy to discuss.Trade-offs
Assert.AreSequenceEqualalready acceptsReadOnlySpan<T>) supports it.ref structparameters mean these can't live behind a single generic, but they don't need to — explicit overloads are the standard pattern.Related
Assert.AreSequenceEqualalready hasReadOnlySpan<T>overloads, so there's a precedent for span-aware assertion APIs.