Skip to content

Apply IAssertionSourceFor pattern to Count(itemAssertion) overloads #5777

@thomhurst

Description

@thomhurst

Follow-up to PR #5764 (issue #5706 fix), which introduced the IAssertionSourceFor<TItem, TSelf> pattern with static-abstract Create for generic Satisfies dispatch.

The same per-type overload duplication still exists in Count(itemAssertion). Issue #5707 closed when the original per-type overloads landed; this issue tracks the architectural follow-up.

Current state

TUnit.Assertions/Extensions/AssertionExtensions.cs:1825-2077 has ~250 LOC of Count(itemAssertion) overloads, each containing inline new XxxAssertion<T>(item, $\"item[{index}]\") construction logic. Pattern matches what PR #5764 refactored away for ItemAt(...).Satisfies(...).

Reference implementation (PR #5764)

  • New interface: TUnit.Assertions/Core/IAssertionSourceFor.cs
  • Generic dispatch entry: Satisfies<TSource> on ListItemAtSource and ReadOnlyListItemAtSource in TUnit.Assertions/Conditions/ListAssertions.cs and ReadOnlyListAssertions.cs
  • Thin per-type delegators: TUnit.Assertions/Extensions/ListItemAtSatisfiesExtensions.cs
  • Sources implement IAssertionSourceFor<TItem, TSelf> with one (or two, for concrete-type variants) static Create methods.

Steps to apply

  1. Add a generic Count<TSource>(Func<TSource, IAssertion?>, ...) instance method on CollectionAssertionBase constrained where TSource : IAssertionSourceFor<TItem, TSource>, mirroring Satisfies<TSource> on ListItemAtSource.
  2. Convert interface-shape Count overloads (IEnumerable, IList, IReadOnlyList, IDictionary, IReadOnlyDictionary, ISet, IReadOnlySet, T[]) into one-line delegators that call source.Count<XxxAssertion<T>>(itemAssertion, expression).
  3. Concrete-type overloads (List<T>, Dictionary<K,V>) require their assertion sources (ListAssertion, MutableDictionaryAssertion) to implement an additional IAssertionSourceFor<ConcreteType, TSelf> variant with a second Create overload — already done for Satisfies in PR Fix item-at Satisfies source typing #5764. Reuse those.
  4. HashSet<T>-typed items already work since HashSetAssertion implements IAssertionSourceFor<HashSet<T>, ...>.
  5. Wrap the new generic entry + interface-shape delegators in #if !NETSTANDARD2_0. netstandard2.0 retains the existing specialised overload behaviour through the current CountSpecialised private helper.

Same pattern applies to

  • FirstItem.Satisfies / LastItem.Satisfies
  • Any future Member, Where, etc. combinators on collection items.

Estimated saving

~150 LOC out of the current ~250 LOC Count overload block.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions