Summary
TUnit.Mocks currently boxes all method arguments into object?[] on every mock invocation:
// Generated today:
public int Add(int a, int b)
{
var __argsArray = new object?[] { a, b }; // allocation + boxing per call
return _engine.HandleCallWithReturn<int>(memberId, "Add", __argsArray, 0);
}
This is the primary reason Imposter beats us on invocation benchmarks (~35% faster for single calls, ~31% for 100 calls).
Proposal
Generate arity-specific HandleCall overloads that avoid the object?[] entirely:
// Generated with this change:
public int Add(int a, int b)
{
return _engine.HandleCallWithReturn<int, int, int>(memberId, "Add", a, b, 0);
}
The engine would have generic overloads like:
public TReturn HandleCallWithReturn<TReturn, T1, T2>(int memberId, string name, T1 arg1, T2 arg2, TReturn defaultValue)
Argument matching would also need typed overloads or a way to match without boxing (e.g., IArgumentMatcher<T>).
Scope
- Source generator changes in
MockImplBuilder to emit typed dispatch
- New generic
HandleCall overloads in MockEngine (up to ~8 type params, fallback to array for more)
- Typed
IArgumentMatcher<T> interface for zero-boxing matching
- Typed
CallRecord or deferred boxing for call history
Benchmark Targets
| Benchmark |
Current |
Target |
| Invocation (single) |
387 ns / 176 B |
<280 ns / <168 B |
| Invocation (100x) |
37.7 μs / 18 KB |
<28 μs / <16 KB |
| Callback |
683 ns / 3.2 KB |
<450 ns / <2.5 KB |
Complexity
High — requires coordinated generator + runtime changes. This is a follow-up to the lower-hanging optimizations (deferred call recording, copy-on-write snapshots, conditional sequencing).
Summary
TUnit.Mocks currently boxes all method arguments into
object?[]on every mock invocation:This is the primary reason Imposter beats us on invocation benchmarks (~35% faster for single calls, ~31% for 100 calls).
Proposal
Generate arity-specific
HandleCalloverloads that avoid theobject?[]entirely:The engine would have generic overloads like:
Argument matching would also need typed overloads or a way to match without boxing (e.g.,
IArgumentMatcher<T>).Scope
MockImplBuilderto emit typed dispatchHandleCalloverloads inMockEngine(up to ~8 type params, fallback to array for more)IArgumentMatcher<T>interface for zero-boxing matchingCallRecordor deferred boxing for call historyBenchmark Targets
Complexity
High — requires coordinated generator + runtime changes. This is a follow-up to the lower-hanging optimizations (deferred call recording, copy-on-write snapshots, conditional sequencing).