Skip to content

Add Span<T>/Memory<T> overloads to Assert.HasCount (and extend MSTEST0037) #9143

@Evangelink

Description

@Evangelink

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

  1. 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.

  2. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/analyzersMSTest.Analyzers Roslyn analyzers and code fixes.
    No fields configured for Feature.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions