From 513658fd0905f2f7747a06e79295185478e58d04 Mon Sep 17 00:00:00 2001
From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com>
Date: Sun, 5 Apr 2026 01:48:06 +0100
Subject: [PATCH 1/8] feat(mocks): add typed ArgumentStore structs for
zero-boxing arg storage
---
TUnit.Mocks/Arguments/ArgumentStore.cs | 213 +++++++++++++++++++++++++
1 file changed, 213 insertions(+)
create mode 100644 TUnit.Mocks/Arguments/ArgumentStore.cs
diff --git a/TUnit.Mocks/Arguments/ArgumentStore.cs b/TUnit.Mocks/Arguments/ArgumentStore.cs
new file mode 100644
index 0000000000..28995dbaff
--- /dev/null
+++ b/TUnit.Mocks/Arguments/ArgumentStore.cs
@@ -0,0 +1,213 @@
+using System.ComponentModel;
+
+namespace TUnit.Mocks.Arguments;
+
+///
+/// Holds typed method arguments and defers boxing until is called.
+/// Public for generated code access. Not intended for direct use.
+///
+[EditorBrowsable(EditorBrowsableState.Never)]
+public interface IArgumentStore
+{
+ /// Gets the number of arguments stored.
+ int Count { get; }
+
+ /// Returns all arguments as a boxed array. Allocates on every call.
+ object?[] ToArray();
+}
+
+///
+[EditorBrowsable(EditorBrowsableState.Never)]
+public readonly struct ArgumentStore(T1 arg1) : IArgumentStore
+{
+ /// The first argument.
+ public readonly T1 Arg1 = arg1;
+
+ ///
+ public int Count => 1;
+
+ ///
+ public object?[] ToArray() => [Arg1];
+}
+
+///
+[EditorBrowsable(EditorBrowsableState.Never)]
+public readonly struct ArgumentStore(T1 arg1, T2 arg2) : IArgumentStore
+{
+ /// The first argument.
+ public readonly T1 Arg1 = arg1;
+
+ /// The second argument.
+ public readonly T2 Arg2 = arg2;
+
+ ///
+ public int Count => 2;
+
+ ///
+ public object?[] ToArray() => [Arg1, Arg2];
+}
+
+///
+[EditorBrowsable(EditorBrowsableState.Never)]
+public readonly struct ArgumentStore(T1 arg1, T2 arg2, T3 arg3) : IArgumentStore
+{
+ /// The first argument.
+ public readonly T1 Arg1 = arg1;
+
+ /// The second argument.
+ public readonly T2 Arg2 = arg2;
+
+ /// The third argument.
+ public readonly T3 Arg3 = arg3;
+
+ ///
+ public int Count => 3;
+
+ ///
+ public object?[] ToArray() => [Arg1, Arg2, Arg3];
+}
+
+///
+[EditorBrowsable(EditorBrowsableState.Never)]
+public readonly struct ArgumentStore(T1 arg1, T2 arg2, T3 arg3, T4 arg4) : IArgumentStore
+{
+ /// The first argument.
+ public readonly T1 Arg1 = arg1;
+
+ /// The second argument.
+ public readonly T2 Arg2 = arg2;
+
+ /// The third argument.
+ public readonly T3 Arg3 = arg3;
+
+ /// The fourth argument.
+ public readonly T4 Arg4 = arg4;
+
+ ///
+ public int Count => 4;
+
+ ///
+ public object?[] ToArray() => [Arg1, Arg2, Arg3, Arg4];
+}
+
+///
+[EditorBrowsable(EditorBrowsableState.Never)]
+public readonly struct ArgumentStore(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) : IArgumentStore
+{
+ /// The first argument.
+ public readonly T1 Arg1 = arg1;
+
+ /// The second argument.
+ public readonly T2 Arg2 = arg2;
+
+ /// The third argument.
+ public readonly T3 Arg3 = arg3;
+
+ /// The fourth argument.
+ public readonly T4 Arg4 = arg4;
+
+ /// The fifth argument.
+ public readonly T5 Arg5 = arg5;
+
+ ///
+ public int Count => 5;
+
+ ///
+ public object?[] ToArray() => [Arg1, Arg2, Arg3, Arg4, Arg5];
+}
+
+///
+[EditorBrowsable(EditorBrowsableState.Never)]
+public readonly struct ArgumentStore(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) : IArgumentStore
+{
+ /// The first argument.
+ public readonly T1 Arg1 = arg1;
+
+ /// The second argument.
+ public readonly T2 Arg2 = arg2;
+
+ /// The third argument.
+ public readonly T3 Arg3 = arg3;
+
+ /// The fourth argument.
+ public readonly T4 Arg4 = arg4;
+
+ /// The fifth argument.
+ public readonly T5 Arg5 = arg5;
+
+ /// The sixth argument.
+ public readonly T6 Arg6 = arg6;
+
+ ///
+ public int Count => 6;
+
+ ///
+ public object?[] ToArray() => [Arg1, Arg2, Arg3, Arg4, Arg5, Arg6];
+}
+
+///
+[EditorBrowsable(EditorBrowsableState.Never)]
+public readonly struct ArgumentStore(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) : IArgumentStore
+{
+ /// The first argument.
+ public readonly T1 Arg1 = arg1;
+
+ /// The second argument.
+ public readonly T2 Arg2 = arg2;
+
+ /// The third argument.
+ public readonly T3 Arg3 = arg3;
+
+ /// The fourth argument.
+ public readonly T4 Arg4 = arg4;
+
+ /// The fifth argument.
+ public readonly T5 Arg5 = arg5;
+
+ /// The sixth argument.
+ public readonly T6 Arg6 = arg6;
+
+ /// The seventh argument.
+ public readonly T7 Arg7 = arg7;
+
+ ///
+ public int Count => 7;
+
+ ///
+ public object?[] ToArray() => [Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7];
+}
+
+///
+[EditorBrowsable(EditorBrowsableState.Never)]
+public readonly struct ArgumentStore(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) : IArgumentStore
+{
+ /// The first argument.
+ public readonly T1 Arg1 = arg1;
+
+ /// The second argument.
+ public readonly T2 Arg2 = arg2;
+
+ /// The third argument.
+ public readonly T3 Arg3 = arg3;
+
+ /// The fourth argument.
+ public readonly T4 Arg4 = arg4;
+
+ /// The fifth argument.
+ public readonly T5 Arg5 = arg5;
+
+ /// The sixth argument.
+ public readonly T6 Arg6 = arg6;
+
+ /// The seventh argument.
+ public readonly T7 Arg7 = arg7;
+
+ /// The eighth argument.
+ public readonly T8 Arg8 = arg8;
+
+ ///
+ public int Count => 8;
+
+ ///
+ public object?[] ToArray() => [Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8];
+}
From 595f6df2a69815b853dbd5d618e483a1dc903f73 Mon Sep 17 00:00:00 2001
From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com>
Date: Sun, 5 Apr 2026 01:50:00 +0100
Subject: [PATCH 2/8] feat(mocks): add typed matching and deferred-boxing
CallRecord
- Refactor CallRecord from record to class with IArgumentStore support
- Add ICapturingMatcher for zero-boxing argument capture
- Add typed Matches and ApplyCaptures to MethodSetup
---
TUnit.Mocks/Arguments/CaptureMatcher.cs | 23 +++-
TUnit.Mocks/Setup/MethodSetup.cs | 149 ++++++++++++++++++++++++
TUnit.Mocks/Verification/CallRecord.cs | 47 +++++++-
3 files changed, 212 insertions(+), 7 deletions(-)
diff --git a/TUnit.Mocks/Arguments/CaptureMatcher.cs b/TUnit.Mocks/Arguments/CaptureMatcher.cs
index 9648c39a60..ae99848d8f 100644
--- a/TUnit.Mocks/Arguments/CaptureMatcher.cs
+++ b/TUnit.Mocks/Arguments/CaptureMatcher.cs
@@ -11,12 +11,20 @@ internal interface ICapturingMatcher
void ApplyCapture(object? value);
}
+///
+/// Generic interface for typed capture without boxing.
+///
+internal interface ICapturingMatcher
+{
+ void ApplyCapture(T? value);
+}
+
///
/// A decorator matcher that delegates to an inner matcher and captures
/// argument values when the inner matcher returns .
/// Every wraps its matcher in this decorator automatically.
///
-internal sealed class CapturingMatcher : IArgumentMatcher, ICapturingMatcher
+internal sealed class CapturingMatcher : IArgumentMatcher, ICapturingMatcher, ICapturingMatcher
{
private readonly IArgumentMatcher _inner;
private ConcurrentQueue? _captured;
@@ -87,5 +95,18 @@ void ICapturingMatcher.ApplyCapture(object? value)
}
}
+ ///
+ /// Typed capture path — avoids boxing for value types.
+ ///
+ void ICapturingMatcher.ApplyCapture(T? value)
+ {
+ if (_captured is null)
+ {
+ Interlocked.CompareExchange(ref _captured, new ConcurrentQueue(), null);
+ }
+
+ _captured.Enqueue(value);
+ }
+
public string Describe() => _inner.Describe();
}
diff --git a/TUnit.Mocks/Setup/MethodSetup.cs b/TUnit.Mocks/Setup/MethodSetup.cs
index 0f48291eca..7eb0d3a2bc 100644
--- a/TUnit.Mocks/Setup/MethodSetup.cs
+++ b/TUnit.Mocks/Setup/MethodSetup.cs
@@ -107,6 +107,155 @@ public bool Matches(object?[] actualArgs)
return true;
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static bool MatchSingle(IArgumentMatcher matcher, T value)
+ {
+ if (matcher is IArgumentMatcher typed)
+ return typed.Matches(value);
+ return matcher.Matches(value);
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool Matches(T1 arg1)
+ {
+ if (_matchers.Length != 1) return false;
+ return MatchSingle(_matchers[0], arg1);
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool Matches(T1 arg1, T2 arg2)
+ {
+ if (_matchers.Length != 2) return false;
+ return MatchSingle(_matchers[0], arg1) && MatchSingle(_matchers[1], arg2);
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool Matches(T1 arg1, T2 arg2, T3 arg3)
+ {
+ if (_matchers.Length != 3) return false;
+ return MatchSingle(_matchers[0], arg1) && MatchSingle(_matchers[1], arg2) && MatchSingle(_matchers[2], arg3);
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool Matches(T1 arg1, T2 arg2, T3 arg3, T4 arg4)
+ {
+ if (_matchers.Length != 4) return false;
+ return MatchSingle(_matchers[0], arg1) && MatchSingle(_matchers[1], arg2) && MatchSingle(_matchers[2], arg3) && MatchSingle(_matchers[3], arg4);
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool Matches(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5)
+ {
+ if (_matchers.Length != 5) return false;
+ return MatchSingle(_matchers[0], arg1) && MatchSingle(_matchers[1], arg2) && MatchSingle(_matchers[2], arg3) && MatchSingle(_matchers[3], arg4) && MatchSingle(_matchers[4], arg5);
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool Matches(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6)
+ {
+ if (_matchers.Length != 6) return false;
+ return MatchSingle(_matchers[0], arg1) && MatchSingle(_matchers[1], arg2) && MatchSingle(_matchers[2], arg3) && MatchSingle(_matchers[3], arg4) && MatchSingle(_matchers[4], arg5) && MatchSingle(_matchers[5], arg6);
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool Matches(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7)
+ {
+ if (_matchers.Length != 7) return false;
+ return MatchSingle(_matchers[0], arg1) && MatchSingle(_matchers[1], arg2) && MatchSingle(_matchers[2], arg3) && MatchSingle(_matchers[3], arg4) && MatchSingle(_matchers[4], arg5) && MatchSingle(_matchers[5], arg6) && MatchSingle(_matchers[6], arg7);
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool Matches(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8)
+ {
+ if (_matchers.Length != 8) return false;
+ return MatchSingle(_matchers[0], arg1) && MatchSingle(_matchers[1], arg2) && MatchSingle(_matchers[2], arg3) && MatchSingle(_matchers[3], arg4) && MatchSingle(_matchers[4], arg5) && MatchSingle(_matchers[5], arg6) && MatchSingle(_matchers[6], arg7) && MatchSingle(_matchers[7], arg8);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void CaptureSingle(IArgumentMatcher matcher, T value)
+ {
+ if (matcher is ICapturingMatcher typed)
+ typed.ApplyCapture(value);
+ else if (matcher is ICapturingMatcher untyped)
+ untyped.ApplyCapture(value);
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void ApplyCaptures(T1 arg1)
+ {
+ if (_matchers.Length >= 1) CaptureSingle(_matchers[0], arg1);
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void ApplyCaptures(T1 arg1, T2 arg2)
+ {
+ if (_matchers.Length >= 1) CaptureSingle(_matchers[0], arg1);
+ if (_matchers.Length >= 2) CaptureSingle(_matchers[1], arg2);
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void ApplyCaptures(T1 arg1, T2 arg2, T3 arg3)
+ {
+ if (_matchers.Length >= 1) CaptureSingle(_matchers[0], arg1);
+ if (_matchers.Length >= 2) CaptureSingle(_matchers[1], arg2);
+ if (_matchers.Length >= 3) CaptureSingle(_matchers[2], arg3);
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void ApplyCaptures(T1 arg1, T2 arg2, T3 arg3, T4 arg4)
+ {
+ if (_matchers.Length >= 1) CaptureSingle(_matchers[0], arg1);
+ if (_matchers.Length >= 2) CaptureSingle(_matchers[1], arg2);
+ if (_matchers.Length >= 3) CaptureSingle(_matchers[2], arg3);
+ if (_matchers.Length >= 4) CaptureSingle(_matchers[3], arg4);
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void ApplyCaptures(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5)
+ {
+ if (_matchers.Length >= 1) CaptureSingle(_matchers[0], arg1);
+ if (_matchers.Length >= 2) CaptureSingle(_matchers[1], arg2);
+ if (_matchers.Length >= 3) CaptureSingle(_matchers[2], arg3);
+ if (_matchers.Length >= 4) CaptureSingle(_matchers[3], arg4);
+ if (_matchers.Length >= 5) CaptureSingle(_matchers[4], arg5);
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void ApplyCaptures(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6)
+ {
+ if (_matchers.Length >= 1) CaptureSingle(_matchers[0], arg1);
+ if (_matchers.Length >= 2) CaptureSingle(_matchers[1], arg2);
+ if (_matchers.Length >= 3) CaptureSingle(_matchers[2], arg3);
+ if (_matchers.Length >= 4) CaptureSingle(_matchers[3], arg4);
+ if (_matchers.Length >= 5) CaptureSingle(_matchers[4], arg5);
+ if (_matchers.Length >= 6) CaptureSingle(_matchers[5], arg6);
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void ApplyCaptures(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7)
+ {
+ if (_matchers.Length >= 1) CaptureSingle(_matchers[0], arg1);
+ if (_matchers.Length >= 2) CaptureSingle(_matchers[1], arg2);
+ if (_matchers.Length >= 3) CaptureSingle(_matchers[2], arg3);
+ if (_matchers.Length >= 4) CaptureSingle(_matchers[3], arg4);
+ if (_matchers.Length >= 5) CaptureSingle(_matchers[4], arg5);
+ if (_matchers.Length >= 6) CaptureSingle(_matchers[5], arg6);
+ if (_matchers.Length >= 7) CaptureSingle(_matchers[6], arg7);
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void ApplyCaptures(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8)
+ {
+ if (_matchers.Length >= 1) CaptureSingle(_matchers[0], arg1);
+ if (_matchers.Length >= 2) CaptureSingle(_matchers[1], arg2);
+ if (_matchers.Length >= 3) CaptureSingle(_matchers[2], arg3);
+ if (_matchers.Length >= 4) CaptureSingle(_matchers[3], arg4);
+ if (_matchers.Length >= 5) CaptureSingle(_matchers[4], arg5);
+ if (_matchers.Length >= 6) CaptureSingle(_matchers[5], arg6);
+ if (_matchers.Length >= 7) CaptureSingle(_matchers[6], arg7);
+ if (_matchers.Length >= 8) CaptureSingle(_matchers[7], arg8);
+ }
+
public void AddEventRaise(EventRaiseInfo raiseInfo)
{
lock (BehaviorLock)
diff --git a/TUnit.Mocks/Verification/CallRecord.cs b/TUnit.Mocks/Verification/CallRecord.cs
index aa9963fdeb..a9dc5f8468 100644
--- a/TUnit.Mocks/Verification/CallRecord.cs
+++ b/TUnit.Mocks/Verification/CallRecord.cs
@@ -1,4 +1,5 @@
using System.ComponentModel;
+using TUnit.Mocks.Arguments;
namespace TUnit.Mocks.Verification;
@@ -6,13 +7,47 @@ namespace TUnit.Mocks.Verification;
/// Records a single method invocation. Public for generated code and verification access.
///
[EditorBrowsable(EditorBrowsableState.Never)]
-public sealed record CallRecord(
- int MemberId,
- string MemberName,
- object?[] Arguments,
- long SequenceNumber
-)
+public sealed class CallRecord
{
+ private readonly IArgumentStore? _store;
+ private object?[]? _arguments;
+
+ ///
+ /// Creates a call record with pre-boxed arguments (fallback path).
+ ///
+ internal CallRecord(int memberId, string memberName, object?[] arguments, long sequenceNumber)
+ {
+ MemberId = memberId;
+ MemberName = memberName;
+ _arguments = arguments;
+ SequenceNumber = sequenceNumber;
+ }
+
+ ///
+ /// Creates a call record with a typed argument store for deferred boxing.
+ ///
+ internal CallRecord(int memberId, string memberName, IArgumentStore store, long sequenceNumber)
+ {
+ MemberId = memberId;
+ MemberName = memberName;
+ _store = store;
+ SequenceNumber = sequenceNumber;
+ }
+
+ /// The unique identifier for the member that was called.
+ public int MemberId { get; }
+
+ /// The name of the member that was called.
+ public string MemberName { get; }
+
+ /// The global sequence number for cross-mock ordering.
+ public long SequenceNumber { get; }
+
+ ///
+ /// The arguments passed to the call. Lazily materialized from the argument store if one was provided.
+ ///
+ public object?[] Arguments => _arguments ??= _store?.ToArray() ?? [];
+
internal bool IsVerifiedField;
///
From aac2656bf79be5ca791d6700b05022efee18d57d Mon Sep 17 00:00:00 2001
From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com>
Date: Sun, 5 Apr 2026 01:55:57 +0100
Subject: [PATCH 3/8] feat(mocks): add typed HandleCall overloads in
MockEngine.Typed.cs
- Make MockEngine partial, add MockEngine.Typed.cs
- 32 typed dispatch methods (4 patterns x 8 arities)
- Typed FindMatchingSetup and RecordCall with IArgumentStore
- Zero boxing on hot path: matching, recording, capture all typed
- Behavior.Execute still boxes via store.ToArray() (cold path)
---
TUnit.Mocks/MockEngine.Typed.cs | 1279 +++++++++++++++++++++++++++++++
TUnit.Mocks/MockEngine.cs | 214 +++++-
2 files changed, 1492 insertions(+), 1 deletion(-)
create mode 100644 TUnit.Mocks/MockEngine.Typed.cs
diff --git a/TUnit.Mocks/MockEngine.Typed.cs b/TUnit.Mocks/MockEngine.Typed.cs
new file mode 100644
index 0000000000..b12307eac6
--- /dev/null
+++ b/TUnit.Mocks/MockEngine.Typed.cs
@@ -0,0 +1,1279 @@
+using TUnit.Mocks.Arguments;
+using TUnit.Mocks.Exceptions;
+using TUnit.Mocks.Setup;
+using TUnit.Mocks.Setup.Behaviors;
+using System.ComponentModel;
+
+namespace TUnit.Mocks;
+
+public sealed partial class MockEngine where T : class
+{
+ // ──────────────────────────────────────────────────────────────────────
+ // Arity 1
+ // ──────────────────────────────────────────────────────────────────────
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void HandleCall(int memberId, string memberName, T1 arg1)
+ {
+ RawReturnContext.Clear();
+ var store = new ArgumentStore(arg1);
+ var callRecord = RecordCall(memberId, memberName, store);
+
+ if (AutoTrackProperties && memberName.StartsWith("set_", StringComparison.Ordinal))
+ {
+ AutoTrackValues[memberName[4..]] = arg1;
+ }
+
+ var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1);
+
+ if (behavior is not null)
+ {
+ var behaviorResult = behavior.Execute(store.ToArray());
+ if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw);
+ try { ApplyMatchedSetup(matchedSetup); }
+ catch { RawReturnContext.Clear(); throw; }
+ return;
+ }
+
+ ApplyMatchedSetup(matchedSetup);
+ if (setupFound) return;
+
+ callRecord.IsUnmatched = true;
+ if (Behavior == MockBehavior.Strict)
+ {
+ throw new MockStrictBehaviorException(FormatCall(memberName, store));
+ }
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public TReturn HandleCallWithReturn(int memberId, string memberName, T1 arg1, TReturn defaultValue)
+ {
+ RawReturnContext.Clear();
+ var store = new ArgumentStore(arg1);
+ var callRecord = RecordCall(memberId, memberName, store);
+
+ var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1);
+
+ if (behavior is not null)
+ {
+ var result = behavior.Execute(store.ToArray());
+ if (result is RawReturn raw) RawReturnContext.Set(raw);
+ try { ApplyMatchedSetup(matchedSetup); }
+ catch { RawReturnContext.Clear(); throw; }
+ if (result is TReturn typed) return typed;
+ if (result is null) return default(TReturn)!;
+ if (result is RawReturn) return defaultValue;
+ throw new InvalidOperationException(
+ $"Setup for method returning {typeof(TReturn).Name} returned incompatible type {result.GetType().Name}.");
+ }
+
+ ApplyMatchedSetup(matchedSetup);
+ if (setupFound) return defaultValue;
+
+ callRecord.IsUnmatched = true;
+
+ if (AutoTrackProperties && Volatile.Read(ref _autoTrackValues) is { } trackValues && memberName.StartsWith("get_", StringComparison.Ordinal))
+ {
+ if (trackValues.TryGetValue(memberName[4..], out var trackedValue))
+ {
+ if (trackedValue is TReturn t) return t;
+ if (trackedValue is null) return default(TReturn)!;
+ }
+ }
+
+#pragma warning disable IL3050, IL2026
+ if (DefaultValueProvider is not null && DefaultValueProvider.CanProvide(typeof(TReturn)))
+ {
+ var customDefault = DefaultValueProvider.GetDefaultValue(typeof(TReturn));
+ if (customDefault is TReturn typedCustom) return typedCustom;
+ if (customDefault is null) return default(TReturn)!;
+ }
+#pragma warning restore IL3050, IL2026
+
+ if (Behavior == MockBehavior.Loose && typeof(TReturn).IsInterface)
+ {
+ var cacheKey = memberName + "|" + typeof(TReturn).FullName;
+ var autoMock = AutoMockCache.GetOrAdd(cacheKey, _ =>
+ {
+ MockRegistry.TryCreateAutoMock(typeof(TReturn), Behavior, out var m);
+ return m;
+ });
+ if (autoMock is not null) return (TReturn)autoMock.ObjectInstance;
+ }
+
+ if (Behavior == MockBehavior.Strict)
+ {
+ throw new MockStrictBehaviorException(FormatCall(memberName, store));
+ }
+
+ return defaultValue;
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool TryHandleCall(int memberId, string memberName, T1 arg1)
+ {
+ RawReturnContext.Clear();
+ var store = new ArgumentStore(arg1);
+ var callRecord = RecordCall(memberId, memberName, store);
+
+ var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1);
+
+ if (behavior is not null)
+ {
+ var behaviorResult = behavior.Execute(store.ToArray());
+ if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw);
+ try { ApplyMatchedSetup(matchedSetup); }
+ catch { RawReturnContext.Clear(); throw; }
+ return true;
+ }
+
+ ApplyMatchedSetup(matchedSetup);
+ if (!setupFound) callRecord.IsUnmatched = true;
+ if (!setupFound && IsWrapMock && Behavior == MockBehavior.Strict)
+ {
+ throw new MockStrictBehaviorException(FormatCall(memberName, store));
+ }
+ return setupFound;
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool TryHandleCallWithReturn(int memberId, string memberName, T1 arg1, TReturn defaultValue, out TReturn result)
+ {
+ RawReturnContext.Clear();
+ var store = new ArgumentStore(arg1);
+ var callRecord = RecordCall(memberId, memberName, store);
+
+ var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1);
+
+ if (behavior is not null)
+ {
+ var behaviorResult = behavior.Execute(store.ToArray());
+ if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw);
+ try { ApplyMatchedSetup(matchedSetup); }
+ catch { RawReturnContext.Clear(); throw; }
+ if (behaviorResult is TReturn typed) result = typed;
+ else if (behaviorResult is null) result = default(TReturn)!;
+ else if (behaviorResult is RawReturn) result = defaultValue;
+ else throw new InvalidOperationException(
+ $"Setup for method returning {typeof(TReturn).Name} returned incompatible type {behaviorResult.GetType().Name}.");
+ return true;
+ }
+
+ ApplyMatchedSetup(matchedSetup);
+ if (setupFound) { result = defaultValue; return true; }
+
+ callRecord.IsUnmatched = true;
+ if (IsWrapMock && Behavior == MockBehavior.Strict)
+ {
+ throw new MockStrictBehaviorException(FormatCall(memberName, store));
+ }
+ result = defaultValue;
+ return false;
+ }
+
+ // ──────────────────────────────────────────────────────────────────────
+ // Arity 2
+ // ──────────────────────────────────────────────────────────────────────
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void HandleCall(int memberId, string memberName, T1 arg1, T2 arg2)
+ {
+ RawReturnContext.Clear();
+ var store = new ArgumentStore(arg1, arg2);
+ var callRecord = RecordCall(memberId, memberName, store);
+
+ var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2);
+
+ if (behavior is not null)
+ {
+ var behaviorResult = behavior.Execute(store.ToArray());
+ if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw);
+ try { ApplyMatchedSetup(matchedSetup); }
+ catch { RawReturnContext.Clear(); throw; }
+ return;
+ }
+
+ ApplyMatchedSetup(matchedSetup);
+ if (setupFound) return;
+
+ callRecord.IsUnmatched = true;
+ if (Behavior == MockBehavior.Strict)
+ {
+ throw new MockStrictBehaviorException(FormatCall(memberName, store));
+ }
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public TReturn HandleCallWithReturn(int memberId, string memberName, T1 arg1, T2 arg2, TReturn defaultValue)
+ {
+ RawReturnContext.Clear();
+ var store = new ArgumentStore(arg1, arg2);
+ var callRecord = RecordCall(memberId, memberName, store);
+
+ var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2);
+
+ if (behavior is not null)
+ {
+ var result = behavior.Execute(store.ToArray());
+ if (result is RawReturn raw) RawReturnContext.Set(raw);
+ try { ApplyMatchedSetup(matchedSetup); }
+ catch { RawReturnContext.Clear(); throw; }
+ if (result is TReturn typed) return typed;
+ if (result is null) return default(TReturn)!;
+ if (result is RawReturn) return defaultValue;
+ throw new InvalidOperationException(
+ $"Setup for method returning {typeof(TReturn).Name} returned incompatible type {result.GetType().Name}.");
+ }
+
+ ApplyMatchedSetup(matchedSetup);
+ if (setupFound) return defaultValue;
+
+ callRecord.IsUnmatched = true;
+
+ if (AutoTrackProperties && Volatile.Read(ref _autoTrackValues) is { } trackValues && memberName.StartsWith("get_", StringComparison.Ordinal))
+ {
+ if (trackValues.TryGetValue(memberName[4..], out var trackedValue))
+ {
+ if (trackedValue is TReturn t) return t;
+ if (trackedValue is null) return default(TReturn)!;
+ }
+ }
+
+#pragma warning disable IL3050, IL2026
+ if (DefaultValueProvider is not null && DefaultValueProvider.CanProvide(typeof(TReturn)))
+ {
+ var customDefault = DefaultValueProvider.GetDefaultValue(typeof(TReturn));
+ if (customDefault is TReturn typedCustom) return typedCustom;
+ if (customDefault is null) return default(TReturn)!;
+ }
+#pragma warning restore IL3050, IL2026
+
+ if (Behavior == MockBehavior.Loose && typeof(TReturn).IsInterface)
+ {
+ var cacheKey = memberName + "|" + typeof(TReturn).FullName;
+ var autoMock = AutoMockCache.GetOrAdd(cacheKey, _ =>
+ {
+ MockRegistry.TryCreateAutoMock(typeof(TReturn), Behavior, out var m);
+ return m;
+ });
+ if (autoMock is not null) return (TReturn)autoMock.ObjectInstance;
+ }
+
+ if (Behavior == MockBehavior.Strict)
+ {
+ throw new MockStrictBehaviorException(FormatCall(memberName, store));
+ }
+
+ return defaultValue;
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool TryHandleCall(int memberId, string memberName, T1 arg1, T2 arg2)
+ {
+ RawReturnContext.Clear();
+ var store = new ArgumentStore(arg1, arg2);
+ var callRecord = RecordCall(memberId, memberName, store);
+
+ var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2);
+
+ if (behavior is not null)
+ {
+ var behaviorResult = behavior.Execute(store.ToArray());
+ if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw);
+ try { ApplyMatchedSetup(matchedSetup); }
+ catch { RawReturnContext.Clear(); throw; }
+ return true;
+ }
+
+ ApplyMatchedSetup(matchedSetup);
+ if (!setupFound) callRecord.IsUnmatched = true;
+ if (!setupFound && IsWrapMock && Behavior == MockBehavior.Strict)
+ {
+ throw new MockStrictBehaviorException(FormatCall(memberName, store));
+ }
+ return setupFound;
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool TryHandleCallWithReturn(int memberId, string memberName, T1 arg1, T2 arg2, TReturn defaultValue, out TReturn result)
+ {
+ RawReturnContext.Clear();
+ var store = new ArgumentStore(arg1, arg2);
+ var callRecord = RecordCall(memberId, memberName, store);
+
+ var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2);
+
+ if (behavior is not null)
+ {
+ var behaviorResult = behavior.Execute(store.ToArray());
+ if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw);
+ try { ApplyMatchedSetup(matchedSetup); }
+ catch { RawReturnContext.Clear(); throw; }
+ if (behaviorResult is TReturn typed) result = typed;
+ else if (behaviorResult is null) result = default(TReturn)!;
+ else if (behaviorResult is RawReturn) result = defaultValue;
+ else throw new InvalidOperationException(
+ $"Setup for method returning {typeof(TReturn).Name} returned incompatible type {behaviorResult.GetType().Name}.");
+ return true;
+ }
+
+ ApplyMatchedSetup(matchedSetup);
+ if (setupFound) { result = defaultValue; return true; }
+
+ callRecord.IsUnmatched = true;
+ if (IsWrapMock && Behavior == MockBehavior.Strict)
+ {
+ throw new MockStrictBehaviorException(FormatCall(memberName, store));
+ }
+ result = defaultValue;
+ return false;
+ }
+
+ // ──────────────────────────────────────────────────────────────────────
+ // Arity 3
+ // ──────────────────────────────────────────────────────────────────────
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void HandleCall(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3)
+ {
+ RawReturnContext.Clear();
+ var store = new ArgumentStore(arg1, arg2, arg3);
+ var callRecord = RecordCall(memberId, memberName, store);
+
+ var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3);
+
+ if (behavior is not null)
+ {
+ var behaviorResult = behavior.Execute(store.ToArray());
+ if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw);
+ try { ApplyMatchedSetup(matchedSetup); }
+ catch { RawReturnContext.Clear(); throw; }
+ return;
+ }
+
+ ApplyMatchedSetup(matchedSetup);
+ if (setupFound) return;
+
+ callRecord.IsUnmatched = true;
+ if (Behavior == MockBehavior.Strict)
+ {
+ throw new MockStrictBehaviorException(FormatCall(memberName, store));
+ }
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public TReturn HandleCallWithReturn(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3, TReturn defaultValue)
+ {
+ RawReturnContext.Clear();
+ var store = new ArgumentStore(arg1, arg2, arg3);
+ var callRecord = RecordCall(memberId, memberName, store);
+
+ var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3);
+
+ if (behavior is not null)
+ {
+ var result = behavior.Execute(store.ToArray());
+ if (result is RawReturn raw) RawReturnContext.Set(raw);
+ try { ApplyMatchedSetup(matchedSetup); }
+ catch { RawReturnContext.Clear(); throw; }
+ if (result is TReturn typed) return typed;
+ if (result is null) return default(TReturn)!;
+ if (result is RawReturn) return defaultValue;
+ throw new InvalidOperationException(
+ $"Setup for method returning {typeof(TReturn).Name} returned incompatible type {result.GetType().Name}.");
+ }
+
+ ApplyMatchedSetup(matchedSetup);
+ if (setupFound) return defaultValue;
+
+ callRecord.IsUnmatched = true;
+
+ if (AutoTrackProperties && Volatile.Read(ref _autoTrackValues) is { } trackValues && memberName.StartsWith("get_", StringComparison.Ordinal))
+ {
+ if (trackValues.TryGetValue(memberName[4..], out var trackedValue))
+ {
+ if (trackedValue is TReturn t) return t;
+ if (trackedValue is null) return default(TReturn)!;
+ }
+ }
+
+#pragma warning disable IL3050, IL2026
+ if (DefaultValueProvider is not null && DefaultValueProvider.CanProvide(typeof(TReturn)))
+ {
+ var customDefault = DefaultValueProvider.GetDefaultValue(typeof(TReturn));
+ if (customDefault is TReturn typedCustom) return typedCustom;
+ if (customDefault is null) return default(TReturn)!;
+ }
+#pragma warning restore IL3050, IL2026
+
+ if (Behavior == MockBehavior.Loose && typeof(TReturn).IsInterface)
+ {
+ var cacheKey = memberName + "|" + typeof(TReturn).FullName;
+ var autoMock = AutoMockCache.GetOrAdd(cacheKey, _ =>
+ {
+ MockRegistry.TryCreateAutoMock(typeof(TReturn), Behavior, out var m);
+ return m;
+ });
+ if (autoMock is not null) return (TReturn)autoMock.ObjectInstance;
+ }
+
+ if (Behavior == MockBehavior.Strict)
+ {
+ throw new MockStrictBehaviorException(FormatCall(memberName, store));
+ }
+
+ return defaultValue;
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool TryHandleCall(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3)
+ {
+ RawReturnContext.Clear();
+ var store = new ArgumentStore(arg1, arg2, arg3);
+ var callRecord = RecordCall(memberId, memberName, store);
+
+ var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3);
+
+ if (behavior is not null)
+ {
+ var behaviorResult = behavior.Execute(store.ToArray());
+ if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw);
+ try { ApplyMatchedSetup(matchedSetup); }
+ catch { RawReturnContext.Clear(); throw; }
+ return true;
+ }
+
+ ApplyMatchedSetup(matchedSetup);
+ if (!setupFound) callRecord.IsUnmatched = true;
+ if (!setupFound && IsWrapMock && Behavior == MockBehavior.Strict)
+ {
+ throw new MockStrictBehaviorException(FormatCall(memberName, store));
+ }
+ return setupFound;
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool TryHandleCallWithReturn(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3, TReturn defaultValue, out TReturn result)
+ {
+ RawReturnContext.Clear();
+ var store = new ArgumentStore(arg1, arg2, arg3);
+ var callRecord = RecordCall(memberId, memberName, store);
+
+ var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3);
+
+ if (behavior is not null)
+ {
+ var behaviorResult = behavior.Execute(store.ToArray());
+ if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw);
+ try { ApplyMatchedSetup(matchedSetup); }
+ catch { RawReturnContext.Clear(); throw; }
+ if (behaviorResult is TReturn typed) result = typed;
+ else if (behaviorResult is null) result = default(TReturn)!;
+ else if (behaviorResult is RawReturn) result = defaultValue;
+ else throw new InvalidOperationException(
+ $"Setup for method returning {typeof(TReturn).Name} returned incompatible type {behaviorResult.GetType().Name}.");
+ return true;
+ }
+
+ ApplyMatchedSetup(matchedSetup);
+ if (setupFound) { result = defaultValue; return true; }
+
+ callRecord.IsUnmatched = true;
+ if (IsWrapMock && Behavior == MockBehavior.Strict)
+ {
+ throw new MockStrictBehaviorException(FormatCall(memberName, store));
+ }
+ result = defaultValue;
+ return false;
+ }
+
+ // ──────────────────────────────────────────────────────────────────────
+ // Arity 4
+ // ──────────────────────────────────────────────────────────────────────
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void HandleCall(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3, T4 arg4)
+ {
+ RawReturnContext.Clear();
+ var store = new ArgumentStore(arg1, arg2, arg3, arg4);
+ var callRecord = RecordCall(memberId, memberName, store);
+
+ var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3, arg4);
+
+ if (behavior is not null)
+ {
+ var behaviorResult = behavior.Execute(store.ToArray());
+ if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw);
+ try { ApplyMatchedSetup(matchedSetup); }
+ catch { RawReturnContext.Clear(); throw; }
+ return;
+ }
+
+ ApplyMatchedSetup(matchedSetup);
+ if (setupFound) return;
+
+ callRecord.IsUnmatched = true;
+ if (Behavior == MockBehavior.Strict)
+ {
+ throw new MockStrictBehaviorException(FormatCall(memberName, store));
+ }
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public TReturn HandleCallWithReturn(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3, T4 arg4, TReturn defaultValue)
+ {
+ RawReturnContext.Clear();
+ var store = new ArgumentStore(arg1, arg2, arg3, arg4);
+ var callRecord = RecordCall(memberId, memberName, store);
+
+ var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3, arg4);
+
+ if (behavior is not null)
+ {
+ var result = behavior.Execute(store.ToArray());
+ if (result is RawReturn raw) RawReturnContext.Set(raw);
+ try { ApplyMatchedSetup(matchedSetup); }
+ catch { RawReturnContext.Clear(); throw; }
+ if (result is TReturn typed) return typed;
+ if (result is null) return default(TReturn)!;
+ if (result is RawReturn) return defaultValue;
+ throw new InvalidOperationException(
+ $"Setup for method returning {typeof(TReturn).Name} returned incompatible type {result.GetType().Name}.");
+ }
+
+ ApplyMatchedSetup(matchedSetup);
+ if (setupFound) return defaultValue;
+
+ callRecord.IsUnmatched = true;
+
+ if (AutoTrackProperties && Volatile.Read(ref _autoTrackValues) is { } trackValues && memberName.StartsWith("get_", StringComparison.Ordinal))
+ {
+ if (trackValues.TryGetValue(memberName[4..], out var trackedValue))
+ {
+ if (trackedValue is TReturn t) return t;
+ if (trackedValue is null) return default(TReturn)!;
+ }
+ }
+
+#pragma warning disable IL3050, IL2026
+ if (DefaultValueProvider is not null && DefaultValueProvider.CanProvide(typeof(TReturn)))
+ {
+ var customDefault = DefaultValueProvider.GetDefaultValue(typeof(TReturn));
+ if (customDefault is TReturn typedCustom) return typedCustom;
+ if (customDefault is null) return default(TReturn)!;
+ }
+#pragma warning restore IL3050, IL2026
+
+ if (Behavior == MockBehavior.Loose && typeof(TReturn).IsInterface)
+ {
+ var cacheKey = memberName + "|" + typeof(TReturn).FullName;
+ var autoMock = AutoMockCache.GetOrAdd(cacheKey, _ =>
+ {
+ MockRegistry.TryCreateAutoMock(typeof(TReturn), Behavior, out var m);
+ return m;
+ });
+ if (autoMock is not null) return (TReturn)autoMock.ObjectInstance;
+ }
+
+ if (Behavior == MockBehavior.Strict)
+ {
+ throw new MockStrictBehaviorException(FormatCall(memberName, store));
+ }
+
+ return defaultValue;
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool TryHandleCall(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3, T4 arg4)
+ {
+ RawReturnContext.Clear();
+ var store = new ArgumentStore(arg1, arg2, arg3, arg4);
+ var callRecord = RecordCall(memberId, memberName, store);
+
+ var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3, arg4);
+
+ if (behavior is not null)
+ {
+ var behaviorResult = behavior.Execute(store.ToArray());
+ if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw);
+ try { ApplyMatchedSetup(matchedSetup); }
+ catch { RawReturnContext.Clear(); throw; }
+ return true;
+ }
+
+ ApplyMatchedSetup(matchedSetup);
+ if (!setupFound) callRecord.IsUnmatched = true;
+ if (!setupFound && IsWrapMock && Behavior == MockBehavior.Strict)
+ {
+ throw new MockStrictBehaviorException(FormatCall(memberName, store));
+ }
+ return setupFound;
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool TryHandleCallWithReturn(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3, T4 arg4, TReturn defaultValue, out TReturn result)
+ {
+ RawReturnContext.Clear();
+ var store = new ArgumentStore(arg1, arg2, arg3, arg4);
+ var callRecord = RecordCall(memberId, memberName, store);
+
+ var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3, arg4);
+
+ if (behavior is not null)
+ {
+ var behaviorResult = behavior.Execute(store.ToArray());
+ if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw);
+ try { ApplyMatchedSetup(matchedSetup); }
+ catch { RawReturnContext.Clear(); throw; }
+ if (behaviorResult is TReturn typed) result = typed;
+ else if (behaviorResult is null) result = default(TReturn)!;
+ else if (behaviorResult is RawReturn) result = defaultValue;
+ else throw new InvalidOperationException(
+ $"Setup for method returning {typeof(TReturn).Name} returned incompatible type {behaviorResult.GetType().Name}.");
+ return true;
+ }
+
+ ApplyMatchedSetup(matchedSetup);
+ if (setupFound) { result = defaultValue; return true; }
+
+ callRecord.IsUnmatched = true;
+ if (IsWrapMock && Behavior == MockBehavior.Strict)
+ {
+ throw new MockStrictBehaviorException(FormatCall(memberName, store));
+ }
+ result = defaultValue;
+ return false;
+ }
+
+ // ──────────────────────────────────────────────────────────────────────
+ // Arity 5
+ // ──────────────────────────────────────────────────────────────────────
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void HandleCall(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5)
+ {
+ RawReturnContext.Clear();
+ var store = new ArgumentStore(arg1, arg2, arg3, arg4, arg5);
+ var callRecord = RecordCall(memberId, memberName, store);
+
+ var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3, arg4, arg5);
+
+ if (behavior is not null)
+ {
+ var behaviorResult = behavior.Execute(store.ToArray());
+ if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw);
+ try { ApplyMatchedSetup(matchedSetup); }
+ catch { RawReturnContext.Clear(); throw; }
+ return;
+ }
+
+ ApplyMatchedSetup(matchedSetup);
+ if (setupFound) return;
+
+ callRecord.IsUnmatched = true;
+ if (Behavior == MockBehavior.Strict)
+ {
+ throw new MockStrictBehaviorException(FormatCall(memberName, store));
+ }
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public TReturn HandleCallWithReturn(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, TReturn defaultValue)
+ {
+ RawReturnContext.Clear();
+ var store = new ArgumentStore(arg1, arg2, arg3, arg4, arg5);
+ var callRecord = RecordCall(memberId, memberName, store);
+
+ var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3, arg4, arg5);
+
+ if (behavior is not null)
+ {
+ var result = behavior.Execute(store.ToArray());
+ if (result is RawReturn raw) RawReturnContext.Set(raw);
+ try { ApplyMatchedSetup(matchedSetup); }
+ catch { RawReturnContext.Clear(); throw; }
+ if (result is TReturn typed) return typed;
+ if (result is null) return default(TReturn)!;
+ if (result is RawReturn) return defaultValue;
+ throw new InvalidOperationException(
+ $"Setup for method returning {typeof(TReturn).Name} returned incompatible type {result.GetType().Name}.");
+ }
+
+ ApplyMatchedSetup(matchedSetup);
+ if (setupFound) return defaultValue;
+
+ callRecord.IsUnmatched = true;
+
+ if (AutoTrackProperties && Volatile.Read(ref _autoTrackValues) is { } trackValues && memberName.StartsWith("get_", StringComparison.Ordinal))
+ {
+ if (trackValues.TryGetValue(memberName[4..], out var trackedValue))
+ {
+ if (trackedValue is TReturn t) return t;
+ if (trackedValue is null) return default(TReturn)!;
+ }
+ }
+
+#pragma warning disable IL3050, IL2026
+ if (DefaultValueProvider is not null && DefaultValueProvider.CanProvide(typeof(TReturn)))
+ {
+ var customDefault = DefaultValueProvider.GetDefaultValue(typeof(TReturn));
+ if (customDefault is TReturn typedCustom) return typedCustom;
+ if (customDefault is null) return default(TReturn)!;
+ }
+#pragma warning restore IL3050, IL2026
+
+ if (Behavior == MockBehavior.Loose && typeof(TReturn).IsInterface)
+ {
+ var cacheKey = memberName + "|" + typeof(TReturn).FullName;
+ var autoMock = AutoMockCache.GetOrAdd(cacheKey, _ =>
+ {
+ MockRegistry.TryCreateAutoMock(typeof(TReturn), Behavior, out var m);
+ return m;
+ });
+ if (autoMock is not null) return (TReturn)autoMock.ObjectInstance;
+ }
+
+ if (Behavior == MockBehavior.Strict)
+ {
+ throw new MockStrictBehaviorException(FormatCall(memberName, store));
+ }
+
+ return defaultValue;
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool TryHandleCall(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5)
+ {
+ RawReturnContext.Clear();
+ var store = new ArgumentStore(arg1, arg2, arg3, arg4, arg5);
+ var callRecord = RecordCall(memberId, memberName, store);
+
+ var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3, arg4, arg5);
+
+ if (behavior is not null)
+ {
+ var behaviorResult = behavior.Execute(store.ToArray());
+ if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw);
+ try { ApplyMatchedSetup(matchedSetup); }
+ catch { RawReturnContext.Clear(); throw; }
+ return true;
+ }
+
+ ApplyMatchedSetup(matchedSetup);
+ if (!setupFound) callRecord.IsUnmatched = true;
+ if (!setupFound && IsWrapMock && Behavior == MockBehavior.Strict)
+ {
+ throw new MockStrictBehaviorException(FormatCall(memberName, store));
+ }
+ return setupFound;
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool TryHandleCallWithReturn(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, TReturn defaultValue, out TReturn result)
+ {
+ RawReturnContext.Clear();
+ var store = new ArgumentStore(arg1, arg2, arg3, arg4, arg5);
+ var callRecord = RecordCall(memberId, memberName, store);
+
+ var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3, arg4, arg5);
+
+ if (behavior is not null)
+ {
+ var behaviorResult = behavior.Execute(store.ToArray());
+ if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw);
+ try { ApplyMatchedSetup(matchedSetup); }
+ catch { RawReturnContext.Clear(); throw; }
+ if (behaviorResult is TReturn typed) result = typed;
+ else if (behaviorResult is null) result = default(TReturn)!;
+ else if (behaviorResult is RawReturn) result = defaultValue;
+ else throw new InvalidOperationException(
+ $"Setup for method returning {typeof(TReturn).Name} returned incompatible type {behaviorResult.GetType().Name}.");
+ return true;
+ }
+
+ ApplyMatchedSetup(matchedSetup);
+ if (setupFound) { result = defaultValue; return true; }
+
+ callRecord.IsUnmatched = true;
+ if (IsWrapMock && Behavior == MockBehavior.Strict)
+ {
+ throw new MockStrictBehaviorException(FormatCall(memberName, store));
+ }
+ result = defaultValue;
+ return false;
+ }
+
+ // ──────────────────────────────────────────────────────────────────────
+ // Arity 6
+ // ──────────────────────────────────────────────────────────────────────
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void HandleCall(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6)
+ {
+ RawReturnContext.Clear();
+ var store = new ArgumentStore(arg1, arg2, arg3, arg4, arg5, arg6);
+ var callRecord = RecordCall(memberId, memberName, store);
+
+ var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3, arg4, arg5, arg6);
+
+ if (behavior is not null)
+ {
+ var behaviorResult = behavior.Execute(store.ToArray());
+ if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw);
+ try { ApplyMatchedSetup(matchedSetup); }
+ catch { RawReturnContext.Clear(); throw; }
+ return;
+ }
+
+ ApplyMatchedSetup(matchedSetup);
+ if (setupFound) return;
+
+ callRecord.IsUnmatched = true;
+ if (Behavior == MockBehavior.Strict)
+ {
+ throw new MockStrictBehaviorException(FormatCall(memberName, store));
+ }
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public TReturn HandleCallWithReturn(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, TReturn defaultValue)
+ {
+ RawReturnContext.Clear();
+ var store = new ArgumentStore(arg1, arg2, arg3, arg4, arg5, arg6);
+ var callRecord = RecordCall(memberId, memberName, store);
+
+ var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3, arg4, arg5, arg6);
+
+ if (behavior is not null)
+ {
+ var result = behavior.Execute(store.ToArray());
+ if (result is RawReturn raw) RawReturnContext.Set(raw);
+ try { ApplyMatchedSetup(matchedSetup); }
+ catch { RawReturnContext.Clear(); throw; }
+ if (result is TReturn typed) return typed;
+ if (result is null) return default(TReturn)!;
+ if (result is RawReturn) return defaultValue;
+ throw new InvalidOperationException(
+ $"Setup for method returning {typeof(TReturn).Name} returned incompatible type {result.GetType().Name}.");
+ }
+
+ ApplyMatchedSetup(matchedSetup);
+ if (setupFound) return defaultValue;
+
+ callRecord.IsUnmatched = true;
+
+ if (AutoTrackProperties && Volatile.Read(ref _autoTrackValues) is { } trackValues && memberName.StartsWith("get_", StringComparison.Ordinal))
+ {
+ if (trackValues.TryGetValue(memberName[4..], out var trackedValue))
+ {
+ if (trackedValue is TReturn t) return t;
+ if (trackedValue is null) return default(TReturn)!;
+ }
+ }
+
+#pragma warning disable IL3050, IL2026
+ if (DefaultValueProvider is not null && DefaultValueProvider.CanProvide(typeof(TReturn)))
+ {
+ var customDefault = DefaultValueProvider.GetDefaultValue(typeof(TReturn));
+ if (customDefault is TReturn typedCustom) return typedCustom;
+ if (customDefault is null) return default(TReturn)!;
+ }
+#pragma warning restore IL3050, IL2026
+
+ if (Behavior == MockBehavior.Loose && typeof(TReturn).IsInterface)
+ {
+ var cacheKey = memberName + "|" + typeof(TReturn).FullName;
+ var autoMock = AutoMockCache.GetOrAdd(cacheKey, _ =>
+ {
+ MockRegistry.TryCreateAutoMock(typeof(TReturn), Behavior, out var m);
+ return m;
+ });
+ if (autoMock is not null) return (TReturn)autoMock.ObjectInstance;
+ }
+
+ if (Behavior == MockBehavior.Strict)
+ {
+ throw new MockStrictBehaviorException(FormatCall(memberName, store));
+ }
+
+ return defaultValue;
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool TryHandleCall(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6)
+ {
+ RawReturnContext.Clear();
+ var store = new ArgumentStore(arg1, arg2, arg3, arg4, arg5, arg6);
+ var callRecord = RecordCall(memberId, memberName, store);
+
+ var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3, arg4, arg5, arg6);
+
+ if (behavior is not null)
+ {
+ var behaviorResult = behavior.Execute(store.ToArray());
+ if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw);
+ try { ApplyMatchedSetup(matchedSetup); }
+ catch { RawReturnContext.Clear(); throw; }
+ return true;
+ }
+
+ ApplyMatchedSetup(matchedSetup);
+ if (!setupFound) callRecord.IsUnmatched = true;
+ if (!setupFound && IsWrapMock && Behavior == MockBehavior.Strict)
+ {
+ throw new MockStrictBehaviorException(FormatCall(memberName, store));
+ }
+ return setupFound;
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool TryHandleCallWithReturn(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, TReturn defaultValue, out TReturn result)
+ {
+ RawReturnContext.Clear();
+ var store = new ArgumentStore(arg1, arg2, arg3, arg4, arg5, arg6);
+ var callRecord = RecordCall(memberId, memberName, store);
+
+ var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3, arg4, arg5, arg6);
+
+ if (behavior is not null)
+ {
+ var behaviorResult = behavior.Execute(store.ToArray());
+ if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw);
+ try { ApplyMatchedSetup(matchedSetup); }
+ catch { RawReturnContext.Clear(); throw; }
+ if (behaviorResult is TReturn typed) result = typed;
+ else if (behaviorResult is null) result = default(TReturn)!;
+ else if (behaviorResult is RawReturn) result = defaultValue;
+ else throw new InvalidOperationException(
+ $"Setup for method returning {typeof(TReturn).Name} returned incompatible type {behaviorResult.GetType().Name}.");
+ return true;
+ }
+
+ ApplyMatchedSetup(matchedSetup);
+ if (setupFound) { result = defaultValue; return true; }
+
+ callRecord.IsUnmatched = true;
+ if (IsWrapMock && Behavior == MockBehavior.Strict)
+ {
+ throw new MockStrictBehaviorException(FormatCall(memberName, store));
+ }
+ result = defaultValue;
+ return false;
+ }
+
+ // ──────────────────────────────────────────────────────────────────────
+ // Arity 7
+ // ──────────────────────────────────────────────────────────────────────
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void HandleCall(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7)
+ {
+ RawReturnContext.Clear();
+ var store = new ArgumentStore(arg1, arg2, arg3, arg4, arg5, arg6, arg7);
+ var callRecord = RecordCall(memberId, memberName, store);
+
+ var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3, arg4, arg5, arg6, arg7);
+
+ if (behavior is not null)
+ {
+ var behaviorResult = behavior.Execute(store.ToArray());
+ if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw);
+ try { ApplyMatchedSetup(matchedSetup); }
+ catch { RawReturnContext.Clear(); throw; }
+ return;
+ }
+
+ ApplyMatchedSetup(matchedSetup);
+ if (setupFound) return;
+
+ callRecord.IsUnmatched = true;
+ if (Behavior == MockBehavior.Strict)
+ {
+ throw new MockStrictBehaviorException(FormatCall(memberName, store));
+ }
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public TReturn HandleCallWithReturn(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, TReturn defaultValue)
+ {
+ RawReturnContext.Clear();
+ var store = new ArgumentStore(arg1, arg2, arg3, arg4, arg5, arg6, arg7);
+ var callRecord = RecordCall(memberId, memberName, store);
+
+ var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3, arg4, arg5, arg6, arg7);
+
+ if (behavior is not null)
+ {
+ var result = behavior.Execute(store.ToArray());
+ if (result is RawReturn raw) RawReturnContext.Set(raw);
+ try { ApplyMatchedSetup(matchedSetup); }
+ catch { RawReturnContext.Clear(); throw; }
+ if (result is TReturn typed) return typed;
+ if (result is null) return default(TReturn)!;
+ if (result is RawReturn) return defaultValue;
+ throw new InvalidOperationException(
+ $"Setup for method returning {typeof(TReturn).Name} returned incompatible type {result.GetType().Name}.");
+ }
+
+ ApplyMatchedSetup(matchedSetup);
+ if (setupFound) return defaultValue;
+
+ callRecord.IsUnmatched = true;
+
+ if (AutoTrackProperties && Volatile.Read(ref _autoTrackValues) is { } trackValues && memberName.StartsWith("get_", StringComparison.Ordinal))
+ {
+ if (trackValues.TryGetValue(memberName[4..], out var trackedValue))
+ {
+ if (trackedValue is TReturn t) return t;
+ if (trackedValue is null) return default(TReturn)!;
+ }
+ }
+
+#pragma warning disable IL3050, IL2026
+ if (DefaultValueProvider is not null && DefaultValueProvider.CanProvide(typeof(TReturn)))
+ {
+ var customDefault = DefaultValueProvider.GetDefaultValue(typeof(TReturn));
+ if (customDefault is TReturn typedCustom) return typedCustom;
+ if (customDefault is null) return default(TReturn)!;
+ }
+#pragma warning restore IL3050, IL2026
+
+ if (Behavior == MockBehavior.Loose && typeof(TReturn).IsInterface)
+ {
+ var cacheKey = memberName + "|" + typeof(TReturn).FullName;
+ var autoMock = AutoMockCache.GetOrAdd(cacheKey, _ =>
+ {
+ MockRegistry.TryCreateAutoMock(typeof(TReturn), Behavior, out var m);
+ return m;
+ });
+ if (autoMock is not null) return (TReturn)autoMock.ObjectInstance;
+ }
+
+ if (Behavior == MockBehavior.Strict)
+ {
+ throw new MockStrictBehaviorException(FormatCall(memberName, store));
+ }
+
+ return defaultValue;
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool TryHandleCall(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7)
+ {
+ RawReturnContext.Clear();
+ var store = new ArgumentStore(arg1, arg2, arg3, arg4, arg5, arg6, arg7);
+ var callRecord = RecordCall(memberId, memberName, store);
+
+ var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3, arg4, arg5, arg6, arg7);
+
+ if (behavior is not null)
+ {
+ var behaviorResult = behavior.Execute(store.ToArray());
+ if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw);
+ try { ApplyMatchedSetup(matchedSetup); }
+ catch { RawReturnContext.Clear(); throw; }
+ return true;
+ }
+
+ ApplyMatchedSetup(matchedSetup);
+ if (!setupFound) callRecord.IsUnmatched = true;
+ if (!setupFound && IsWrapMock && Behavior == MockBehavior.Strict)
+ {
+ throw new MockStrictBehaviorException(FormatCall(memberName, store));
+ }
+ return setupFound;
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool TryHandleCallWithReturn(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, TReturn defaultValue, out TReturn result)
+ {
+ RawReturnContext.Clear();
+ var store = new ArgumentStore(arg1, arg2, arg3, arg4, arg5, arg6, arg7);
+ var callRecord = RecordCall(memberId, memberName, store);
+
+ var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3, arg4, arg5, arg6, arg7);
+
+ if (behavior is not null)
+ {
+ var behaviorResult = behavior.Execute(store.ToArray());
+ if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw);
+ try { ApplyMatchedSetup(matchedSetup); }
+ catch { RawReturnContext.Clear(); throw; }
+ if (behaviorResult is TReturn typed) result = typed;
+ else if (behaviorResult is null) result = default(TReturn)!;
+ else if (behaviorResult is RawReturn) result = defaultValue;
+ else throw new InvalidOperationException(
+ $"Setup for method returning {typeof(TReturn).Name} returned incompatible type {behaviorResult.GetType().Name}.");
+ return true;
+ }
+
+ ApplyMatchedSetup(matchedSetup);
+ if (setupFound) { result = defaultValue; return true; }
+
+ callRecord.IsUnmatched = true;
+ if (IsWrapMock && Behavior == MockBehavior.Strict)
+ {
+ throw new MockStrictBehaviorException(FormatCall(memberName, store));
+ }
+ result = defaultValue;
+ return false;
+ }
+
+ // ──────────────────────────────────────────────────────────────────────
+ // Arity 8
+ // ──────────────────────────────────────────────────────────────────────
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void HandleCall(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8)
+ {
+ RawReturnContext.Clear();
+ var store = new ArgumentStore(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8);
+ var callRecord = RecordCall(memberId, memberName, store);
+
+ var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8);
+
+ if (behavior is not null)
+ {
+ var behaviorResult = behavior.Execute(store.ToArray());
+ if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw);
+ try { ApplyMatchedSetup(matchedSetup); }
+ catch { RawReturnContext.Clear(); throw; }
+ return;
+ }
+
+ ApplyMatchedSetup(matchedSetup);
+ if (setupFound) return;
+
+ callRecord.IsUnmatched = true;
+ if (Behavior == MockBehavior.Strict)
+ {
+ throw new MockStrictBehaviorException(FormatCall(memberName, store));
+ }
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public TReturn HandleCallWithReturn(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, TReturn defaultValue)
+ {
+ RawReturnContext.Clear();
+ var store = new ArgumentStore(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8);
+ var callRecord = RecordCall(memberId, memberName, store);
+
+ var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8);
+
+ if (behavior is not null)
+ {
+ var result = behavior.Execute(store.ToArray());
+ if (result is RawReturn raw) RawReturnContext.Set(raw);
+ try { ApplyMatchedSetup(matchedSetup); }
+ catch { RawReturnContext.Clear(); throw; }
+ if (result is TReturn typed) return typed;
+ if (result is null) return default(TReturn)!;
+ if (result is RawReturn) return defaultValue;
+ throw new InvalidOperationException(
+ $"Setup for method returning {typeof(TReturn).Name} returned incompatible type {result.GetType().Name}.");
+ }
+
+ ApplyMatchedSetup(matchedSetup);
+ if (setupFound) return defaultValue;
+
+ callRecord.IsUnmatched = true;
+
+ if (AutoTrackProperties && Volatile.Read(ref _autoTrackValues) is { } trackValues && memberName.StartsWith("get_", StringComparison.Ordinal))
+ {
+ if (trackValues.TryGetValue(memberName[4..], out var trackedValue))
+ {
+ if (trackedValue is TReturn t) return t;
+ if (trackedValue is null) return default(TReturn)!;
+ }
+ }
+
+#pragma warning disable IL3050, IL2026
+ if (DefaultValueProvider is not null && DefaultValueProvider.CanProvide(typeof(TReturn)))
+ {
+ var customDefault = DefaultValueProvider.GetDefaultValue(typeof(TReturn));
+ if (customDefault is TReturn typedCustom) return typedCustom;
+ if (customDefault is null) return default(TReturn)!;
+ }
+#pragma warning restore IL3050, IL2026
+
+ if (Behavior == MockBehavior.Loose && typeof(TReturn).IsInterface)
+ {
+ var cacheKey = memberName + "|" + typeof(TReturn).FullName;
+ var autoMock = AutoMockCache.GetOrAdd(cacheKey, _ =>
+ {
+ MockRegistry.TryCreateAutoMock(typeof(TReturn), Behavior, out var m);
+ return m;
+ });
+ if (autoMock is not null) return (TReturn)autoMock.ObjectInstance;
+ }
+
+ if (Behavior == MockBehavior.Strict)
+ {
+ throw new MockStrictBehaviorException(FormatCall(memberName, store));
+ }
+
+ return defaultValue;
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool TryHandleCall(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8)
+ {
+ RawReturnContext.Clear();
+ var store = new ArgumentStore(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8);
+ var callRecord = RecordCall(memberId, memberName, store);
+
+ var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8);
+
+ if (behavior is not null)
+ {
+ var behaviorResult = behavior.Execute(store.ToArray());
+ if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw);
+ try { ApplyMatchedSetup(matchedSetup); }
+ catch { RawReturnContext.Clear(); throw; }
+ return true;
+ }
+
+ ApplyMatchedSetup(matchedSetup);
+ if (!setupFound) callRecord.IsUnmatched = true;
+ if (!setupFound && IsWrapMock && Behavior == MockBehavior.Strict)
+ {
+ throw new MockStrictBehaviorException(FormatCall(memberName, store));
+ }
+ return setupFound;
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool TryHandleCallWithReturn(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, TReturn defaultValue, out TReturn result)
+ {
+ RawReturnContext.Clear();
+ var store = new ArgumentStore(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8);
+ var callRecord = RecordCall(memberId, memberName, store);
+
+ var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8);
+
+ if (behavior is not null)
+ {
+ var behaviorResult = behavior.Execute(store.ToArray());
+ if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw);
+ try { ApplyMatchedSetup(matchedSetup); }
+ catch { RawReturnContext.Clear(); throw; }
+ if (behaviorResult is TReturn typed) result = typed;
+ else if (behaviorResult is null) result = default(TReturn)!;
+ else if (behaviorResult is RawReturn) result = defaultValue;
+ else throw new InvalidOperationException(
+ $"Setup for method returning {typeof(TReturn).Name} returned incompatible type {behaviorResult.GetType().Name}.");
+ return true;
+ }
+
+ ApplyMatchedSetup(matchedSetup);
+ if (setupFound) { result = defaultValue; return true; }
+
+ callRecord.IsUnmatched = true;
+ if (IsWrapMock && Behavior == MockBehavior.Strict)
+ {
+ throw new MockStrictBehaviorException(FormatCall(memberName, store));
+ }
+ result = defaultValue;
+ return false;
+ }
+}
diff --git a/TUnit.Mocks/MockEngine.cs b/TUnit.Mocks/MockEngine.cs
index bb9bebf91b..5d25267dba 100644
--- a/TUnit.Mocks/MockEngine.cs
+++ b/TUnit.Mocks/MockEngine.cs
@@ -21,7 +21,7 @@ internal static class MockCallSequence
}
[EditorBrowsable(EditorBrowsableState.Never)]
-public sealed class MockEngine : IMockEngineAccess where T : class
+public sealed partial class MockEngine : IMockEngineAccess where T : class
{
// Single lock for both setup and call mutations — reduces allocation by one Lock object.
// Contention is acceptable since setup and call recording rarely overlap in typical usage.
@@ -737,6 +737,20 @@ private CallRecord RecordCall(int memberId, string memberName, object?[] args)
return record;
}
+ private CallRecord RecordCall(int memberId, string memberName, IArgumentStore store)
+ {
+ var seq = MockCallSequence.Next();
+ var record = new CallRecord(memberId, memberName, store, seq);
+ lock (Lock)
+ {
+ EnsureCallArrayCapacity(memberId);
+ var memberCalls = _callsByMemberId![memberId] ??= new();
+ memberCalls.Add(record);
+ _callCountByMemberId![memberId]++;
+ }
+ return record;
+ }
+
[MethodImpl(MethodImplOptions.NoInlining)]
private void EnsureCallArrayCapacity(int memberId)
{
@@ -894,9 +908,207 @@ private void RebuildStaleSnapshots()
return (false, null, null);
}
+ private (bool SetupFound, IBehavior? Behavior, MethodSetup? Setup) FindMatchingSetup(int memberId, T1 arg1)
+ {
+ if (_hasStaleSetups) RebuildStaleSnapshots();
+ if (_hasStatefulSetups)
+ return FindMatchingSetupLocked(memberId, [arg1]);
+
+ var snapshot = _setupsByMemberId;
+ if (snapshot is null || (uint)memberId >= (uint)snapshot.Length) return (false, null, null);
+ var setups = snapshot[memberId];
+ if (setups is null) return (false, null, null);
+
+ for (int i = setups.Length - 1; i >= 0; i--)
+ {
+ var setup = setups[i];
+ if (setup.Matches(arg1))
+ {
+ setup.IncrementInvokeCount();
+ setup.ApplyCaptures(arg1);
+ return (true, setup.GetNextBehavior(), setup);
+ }
+ }
+ return (false, null, null);
+ }
+
+ private (bool SetupFound, IBehavior? Behavior, MethodSetup? Setup) FindMatchingSetup(int memberId, T1 arg1, T2 arg2)
+ {
+ if (_hasStaleSetups) RebuildStaleSnapshots();
+ if (_hasStatefulSetups)
+ return FindMatchingSetupLocked(memberId, [arg1, arg2]);
+
+ var snapshot = _setupsByMemberId;
+ if (snapshot is null || (uint)memberId >= (uint)snapshot.Length) return (false, null, null);
+ var setups = snapshot[memberId];
+ if (setups is null) return (false, null, null);
+
+ for (int i = setups.Length - 1; i >= 0; i--)
+ {
+ var setup = setups[i];
+ if (setup.Matches(arg1, arg2))
+ {
+ setup.IncrementInvokeCount();
+ setup.ApplyCaptures(arg1, arg2);
+ return (true, setup.GetNextBehavior(), setup);
+ }
+ }
+ return (false, null, null);
+ }
+
+ private (bool SetupFound, IBehavior? Behavior, MethodSetup? Setup) FindMatchingSetup(int memberId, T1 arg1, T2 arg2, T3 arg3)
+ {
+ if (_hasStaleSetups) RebuildStaleSnapshots();
+ if (_hasStatefulSetups)
+ return FindMatchingSetupLocked(memberId, [arg1, arg2, arg3]);
+
+ var snapshot = _setupsByMemberId;
+ if (snapshot is null || (uint)memberId >= (uint)snapshot.Length) return (false, null, null);
+ var setups = snapshot[memberId];
+ if (setups is null) return (false, null, null);
+
+ for (int i = setups.Length - 1; i >= 0; i--)
+ {
+ var setup = setups[i];
+ if (setup.Matches(arg1, arg2, arg3))
+ {
+ setup.IncrementInvokeCount();
+ setup.ApplyCaptures(arg1, arg2, arg3);
+ return (true, setup.GetNextBehavior(), setup);
+ }
+ }
+ return (false, null, null);
+ }
+
+ private (bool SetupFound, IBehavior? Behavior, MethodSetup? Setup) FindMatchingSetup(int memberId, T1 arg1, T2 arg2, T3 arg3, T4 arg4)
+ {
+ if (_hasStaleSetups) RebuildStaleSnapshots();
+ if (_hasStatefulSetups)
+ return FindMatchingSetupLocked(memberId, [arg1, arg2, arg3, arg4]);
+
+ var snapshot = _setupsByMemberId;
+ if (snapshot is null || (uint)memberId >= (uint)snapshot.Length) return (false, null, null);
+ var setups = snapshot[memberId];
+ if (setups is null) return (false, null, null);
+
+ for (int i = setups.Length - 1; i >= 0; i--)
+ {
+ var setup = setups[i];
+ if (setup.Matches(arg1, arg2, arg3, arg4))
+ {
+ setup.IncrementInvokeCount();
+ setup.ApplyCaptures(arg1, arg2, arg3, arg4);
+ return (true, setup.GetNextBehavior(), setup);
+ }
+ }
+ return (false, null, null);
+ }
+
+ private (bool SetupFound, IBehavior? Behavior, MethodSetup? Setup) FindMatchingSetup(int memberId, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5)
+ {
+ if (_hasStaleSetups) RebuildStaleSnapshots();
+ if (_hasStatefulSetups)
+ return FindMatchingSetupLocked(memberId, [arg1, arg2, arg3, arg4, arg5]);
+
+ var snapshot = _setupsByMemberId;
+ if (snapshot is null || (uint)memberId >= (uint)snapshot.Length) return (false, null, null);
+ var setups = snapshot[memberId];
+ if (setups is null) return (false, null, null);
+
+ for (int i = setups.Length - 1; i >= 0; i--)
+ {
+ var setup = setups[i];
+ if (setup.Matches(arg1, arg2, arg3, arg4, arg5))
+ {
+ setup.IncrementInvokeCount();
+ setup.ApplyCaptures(arg1, arg2, arg3, arg4, arg5);
+ return (true, setup.GetNextBehavior(), setup);
+ }
+ }
+ return (false, null, null);
+ }
+
+ private (bool SetupFound, IBehavior? Behavior, MethodSetup? Setup) FindMatchingSetup(int memberId, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6)
+ {
+ if (_hasStaleSetups) RebuildStaleSnapshots();
+ if (_hasStatefulSetups)
+ return FindMatchingSetupLocked(memberId, [arg1, arg2, arg3, arg4, arg5, arg6]);
+
+ var snapshot = _setupsByMemberId;
+ if (snapshot is null || (uint)memberId >= (uint)snapshot.Length) return (false, null, null);
+ var setups = snapshot[memberId];
+ if (setups is null) return (false, null, null);
+
+ for (int i = setups.Length - 1; i >= 0; i--)
+ {
+ var setup = setups[i];
+ if (setup.Matches(arg1, arg2, arg3, arg4, arg5, arg6))
+ {
+ setup.IncrementInvokeCount();
+ setup.ApplyCaptures(arg1, arg2, arg3, arg4, arg5, arg6);
+ return (true, setup.GetNextBehavior(), setup);
+ }
+ }
+ return (false, null, null);
+ }
+
+ private (bool SetupFound, IBehavior? Behavior, MethodSetup? Setup) FindMatchingSetup(int memberId, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7)
+ {
+ if (_hasStaleSetups) RebuildStaleSnapshots();
+ if (_hasStatefulSetups)
+ return FindMatchingSetupLocked(memberId, [arg1, arg2, arg3, arg4, arg5, arg6, arg7]);
+
+ var snapshot = _setupsByMemberId;
+ if (snapshot is null || (uint)memberId >= (uint)snapshot.Length) return (false, null, null);
+ var setups = snapshot[memberId];
+ if (setups is null) return (false, null, null);
+
+ for (int i = setups.Length - 1; i >= 0; i--)
+ {
+ var setup = setups[i];
+ if (setup.Matches(arg1, arg2, arg3, arg4, arg5, arg6, arg7))
+ {
+ setup.IncrementInvokeCount();
+ setup.ApplyCaptures(arg1, arg2, arg3, arg4, arg5, arg6, arg7);
+ return (true, setup.GetNextBehavior(), setup);
+ }
+ }
+ return (false, null, null);
+ }
+
+ private (bool SetupFound, IBehavior? Behavior, MethodSetup? Setup) FindMatchingSetup(int memberId, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8)
+ {
+ if (_hasStaleSetups) RebuildStaleSnapshots();
+ if (_hasStatefulSetups)
+ return FindMatchingSetupLocked(memberId, [arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8]);
+
+ var snapshot = _setupsByMemberId;
+ if (snapshot is null || (uint)memberId >= (uint)snapshot.Length) return (false, null, null);
+ var setups = snapshot[memberId];
+ if (setups is null) return (false, null, null);
+
+ for (int i = setups.Length - 1; i >= 0; i--)
+ {
+ var setup = setups[i];
+ if (setup.Matches(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8))
+ {
+ setup.IncrementInvokeCount();
+ setup.ApplyCaptures(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8);
+ return (true, setup.GetNextBehavior(), setup);
+ }
+ }
+ return (false, null, null);
+ }
+
private static string FormatCall(string memberName, object?[] args)
{
var formattedArgs = string.Join(", ", args.Select(a => a?.ToString() ?? "null"));
return $"{memberName}({formattedArgs})";
}
+
+ private static string FormatCall(string memberName, IArgumentStore store)
+ {
+ var formattedArgs = string.Join(", ", store.ToArray().Select(a => a?.ToString() ?? "null"));
+ return $"{memberName}({formattedArgs})";
+ }
}
From f89a88c0b6daef99b02b87f093034cac3778e87a Mon Sep 17 00:00:00 2001
From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com>
Date: Sun, 5 Apr 2026 02:00:15 +0100
Subject: [PATCH 4/8] feat(mocks): source generator emits typed HandleCall
dispatch for arity 1-8
Methods with 1-8 non-out, non-ref-struct parameters now call typed
HandleCall overloads, eliminating object?[] allocation and boxing.
Methods with >8 params or ref-struct params fall back to existing path.
---
.../Builders/MockImplBuilder.cs | 200 +++++++++++++++---
1 file changed, 173 insertions(+), 27 deletions(-)
diff --git a/TUnit.Mocks.SourceGenerator/Builders/MockImplBuilder.cs b/TUnit.Mocks.SourceGenerator/Builders/MockImplBuilder.cs
index 158bec78f6..100cbf941b 100644
--- a/TUnit.Mocks.SourceGenerator/Builders/MockImplBuilder.cs
+++ b/TUnit.Mocks.SourceGenerator/Builders/MockImplBuilder.cs
@@ -213,12 +213,29 @@ private static void GenerateWrapMethodBody(CodeWriter writer, MockMemberModel me
}
}
- var argsArray = EmitArgsArrayVariable(writer, method);
+ var useTypedDispatch = CanUseTypedDispatch(method);
+ string? argsArray = null;
+ string? typeArgs = null;
+ string? argsList = null;
+
+ if (useTypedDispatch)
+ {
+ typeArgs = GetTypedArgsGenericParams(method);
+ argsList = GetTypedArgsList(method);
+ }
+ else
+ {
+ argsArray = EmitArgsArrayVariable(writer, method);
+ }
+
var argPassList = GetArgPassList(method);
if (method.IsVoid && !method.IsAsync)
{
- writer.AppendLine($"if (_engine.TryHandleCall({method.MemberId}, \"{method.Name}\", {argsArray}))");
+ if (useTypedDispatch)
+ writer.AppendLine($"if (_engine.TryHandleCall<{typeArgs}>({method.MemberId}, \"{method.Name}\", {argsList}))");
+ else
+ writer.AppendLine($"if (_engine.TryHandleCall({method.MemberId}, \"{method.Name}\", {argsArray}))");
writer.AppendLine("{");
writer.IncreaseIndent();
EmitOutRefReadback(writer, method);
@@ -229,7 +246,10 @@ private static void GenerateWrapMethodBody(CodeWriter writer, MockMemberModel me
}
else if (method.IsVoid && method.IsAsync)
{
- writer.AppendLine($"if (_engine.TryHandleCall({method.MemberId}, \"{method.Name}\", {argsArray}))");
+ if (useTypedDispatch)
+ writer.AppendLine($"if (_engine.TryHandleCall<{typeArgs}>({method.MemberId}, \"{method.Name}\", {argsList}))");
+ else
+ writer.AppendLine($"if (_engine.TryHandleCall({method.MemberId}, \"{method.Name}\", {argsArray}))");
writer.AppendLine("{");
writer.IncreaseIndent();
EmitOutRefReadback(writer, method);
@@ -249,14 +269,20 @@ private static void GenerateWrapMethodBody(CodeWriter writer, MockMemberModel me
{
if (method.IsReturnTypeStaticAbstractInterface)
{
- writer.AppendLine($"if (_engine.TryHandleCallWithReturn
- private static bool CanUseTypedDispatch(MockMemberModel method)
+ private static (bool IsTyped, string? TypeArgs, string? ArgsList) GetTypedDispatchInfo(MockMemberModel method)
{
- if (method.HasRefStructParams) return false;
+ if (method.HasRefStructParams) return (false, null, null);
var nonOutParams = method.Parameters.Where(p => p.Direction != ParameterDirection.Out).ToList();
- return nonOutParams.Count is >= 1 and <= 8;
- }
-
- ///
- /// Gets the generic type parameters for typed dispatch (e.g., "int, string" for a method with int and string args).
- ///
- private static string GetTypedArgsGenericParams(MockMemberModel method)
- {
- var nonOutParams = method.Parameters.Where(p => p.Direction != ParameterDirection.Out);
- return string.Join(", ", nonOutParams.Select(p => p.FullyQualifiedType));
+ if (nonOutParams.Count is < 1 or > 8) return (false, null, null);
+ var typeArgs = string.Join(", ", nonOutParams.Select(p => p.FullyQualifiedType));
+ var argsList = string.Join(", ", nonOutParams.Select(p => p.Name));
+ return (true, typeArgs, argsList);
}
- ///
- /// Gets the argument list for typed dispatch (e.g., "a, b" for parameters named a and b).
- ///
- private static string GetTypedArgsList(MockMemberModel method)
- {
- var nonOutParams = method.Parameters.Where(p => p.Direction != ParameterDirection.Out);
- return string.Join(", ", nonOutParams.Select(p => p.Name));
- }
+ /// Emits a HandleCall or TryHandleCall invocation, choosing typed or fallback path.
+ private static string EmitHandleCall(bool isTyped, string? typeArgs, string? argsList, string? argsArray, int memberId, string memberName)
+ => isTyped
+ ? $"_engine.HandleCall<{typeArgs}>({memberId}, \"{memberName}\", {argsList})"
+ : $"_engine.HandleCall({memberId}, \"{memberName}\", {argsArray})";
+
+ /// Emits a HandleCallWithReturn invocation, choosing typed or fallback path.
+ private static string EmitHandleCallWithReturn(bool isTyped, string? typeArgs, string? argsList, string? argsArray, string returnTypeArg, int memberId, string memberName, string defaultValue)
+ => isTyped
+ ? $"_engine.HandleCallWithReturn<{returnTypeArg}, {typeArgs}>({memberId}, \"{memberName}\", {argsList}, {defaultValue})"
+ : $"_engine.HandleCallWithReturn<{returnTypeArg}>({memberId}, \"{memberName}\", {argsArray}, {defaultValue})";
+
+ /// Emits a TryHandleCall condition, choosing typed or fallback path.
+ private static string EmitTryHandleCall(bool isTyped, string? typeArgs, string? argsList, string? argsArray, int memberId, string memberName)
+ => isTyped
+ ? $"_engine.TryHandleCall<{typeArgs}>({memberId}, \"{memberName}\", {argsList})"
+ : $"_engine.TryHandleCall({memberId}, \"{memberName}\", {argsArray})";
+
+ /// Emits a TryHandleCallWithReturn condition, choosing typed or fallback path.
+ private static string EmitTryHandleCallWithReturn(bool isTyped, string? typeArgs, string? argsList, string? argsArray, string returnTypeArg, int memberId, string memberName, string defaultValue, string outVar)
+ => isTyped
+ ? $"_engine.TryHandleCallWithReturn<{returnTypeArg}, {typeArgs}>({memberId}, \"{memberName}\", {argsList}, {defaultValue}, out var {outVar})"
+ : $"_engine.TryHandleCallWithReturn<{returnTypeArg}>({memberId}, \"{memberName}\", {argsArray}, {defaultValue}, out var {outVar})";
///
/// Returns true if the method has any out or ref parameters that need read-back.
diff --git a/TUnit.Mocks/Verification/CallRecord.cs b/TUnit.Mocks/Verification/CallRecord.cs
index 78e1fb9a4d..3741aa43ad 100644
--- a/TUnit.Mocks/Verification/CallRecord.cs
+++ b/TUnit.Mocks/Verification/CallRecord.cs
@@ -48,7 +48,15 @@ public CallRecord(int memberId, string memberName, IArgumentStore store, long se
///
/// The arguments passed to the call. Lazily materialized from the argument store if one was provided.
///
- public object?[] Arguments => _arguments ??= _store?.ToArray() ?? [];
+ public object?[] Arguments
+ {
+ get
+ {
+ if (_arguments is not null) return _arguments;
+ var arr = _store?.ToArray() ?? [];
+ return Interlocked.CompareExchange(ref _arguments, arr, null) ?? arr;
+ }
+ }
internal bool IsVerifiedField;