Skip to content

Apply IAssertionSourceFor pattern to Count(itemAssertion) overloads #5777

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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions