From 6d2c63891745cd0c61235698f73434be8e88dce8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Breu=C3=9F?= Date: Sun, 10 May 2026 09:47:07 +0200 Subject: [PATCH 1/2] docs: document covariant parameter matching Parameter matchers are covariant in their type argument, but the parameter-matching page never said so. Add a Covariant Type Matching subsection alongside Custom Equality Comparers (the other meta-feature that spans matcher families) with a chocolate-themed example showing both setup and verify narrowing on a derived type. --- Docs/pages/setup/04-parameter-matching.md | 28 +++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/Docs/pages/setup/04-parameter-matching.md b/Docs/pages/setup/04-parameter-matching.md index 1d85cc57..af418d03 100644 --- a/Docs/pages/setup/04-parameter-matching.md +++ b/Docs/pages/setup/04-parameter-matching.md @@ -120,6 +120,34 @@ int result = sut.Process("HELLO"); // result == 42 ``` +### Covariant Type Matching + +Parameter matchers are covariant in their type argument: when a method declares a parameter of a base type, you can +narrow the match by supplying a matcher for a derived type. Only calls whose actual argument is an instance of that +derived type will match — calls passing sibling or base-only typed values fall through to other setups. + +```csharp +public abstract class Chocolate { } +public class DarkChocolate : Chocolate { } +public class MilkChocolate : Chocolate { } + +public interface IChocolateBox +{ + bool Add(Chocolate chocolate); +} + +IChocolateBox sut = IChocolateBox.CreateMock(); + +// Narrow the match to DarkChocolate, even though Add accepts any Chocolate. +sut.Mock.Setup.Add(It.IsAny()).Returns(true); + +bool dark = sut.Add(new DarkChocolate()); // true, matched the DarkChocolate setup +bool milk = sut.Add(new MilkChocolate()); // false, no setup matched -> default + +// Verifications are covariant too — only dark-chocolate additions are counted. +sut.Mock.Verify.Add(It.IsAny()).Once(); +``` + ### Span Parameters (.NET 8+) - `It.IsSpan(predicate)`: Matches `Span` parameters that satisfy the predicate. From ad0357e8fda74805232eab33a3c47b0c0ebaf3a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Breu=C3=9F?= Date: Sun, 10 May 2026 10:06:42 +0200 Subject: [PATCH 2/2] docs: clarify fall-through wording for covariant matchers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The example uses an abstract base, so there can be no base-typed instance — only sibling derived types fall through. Reword the paragraph to refer to "other runtime types" so it matches what the example actually demonstrates. Co-Authored-By: Claude Opus 4.7 (1M context) --- Docs/pages/setup/04-parameter-matching.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Docs/pages/setup/04-parameter-matching.md b/Docs/pages/setup/04-parameter-matching.md index af418d03..b00bde23 100644 --- a/Docs/pages/setup/04-parameter-matching.md +++ b/Docs/pages/setup/04-parameter-matching.md @@ -124,7 +124,7 @@ int result = sut.Process("HELLO"); Parameter matchers are covariant in their type argument: when a method declares a parameter of a base type, you can narrow the match by supplying a matcher for a derived type. Only calls whose actual argument is an instance of that -derived type will match — calls passing sibling or base-only typed values fall through to other setups. +derived type (or a further-derived type) will match — calls passing other runtime types fall through to other setups. ```csharp public abstract class Chocolate { }