Interface versus generic constraint with regard to structs

The other day I asked the following on Twitter:

Of course, with my minuscule follow count, I couldn’t really expect an answer (no, I’m not bitter, it’s not that I put any energy into growing that count), so I had to answer the question myself, or rather, confirm what I thought was correct. I wrote the following 2 methods:

public interface IPrimitive<T> { T Value { get; } }
readonly record struct UserId(string Value) : IPrimitive<string>;
readonly record struct ProductId(string Value) : IPrimitive<string>;
static string SwitchOnValue1<T>(T value) where T : IPrimitive<string> =>
value switch
{
UserId { Value: "user-123" } => "It's you, Ralph",
ProductId { Value: "product-123" } => "It's Ralph's product",
_ => throw new NotSupportedException()
};
static string SwitchOnValue2(IPrimitive<string> value) =>
// identical to the first version

Notice the different argument type and the first version being generic while the second isn’t. With the wonderful BenchmarkDotNet, I set up two benchmarks that would call each version with the same structs…

[Benchmark]
public void CallingTheSwitch1()
{
var result = SwitchOnValue1(new UserId("user-123"));
result = SwitchOnValue1(new ProductId("product-123"));
}
[Benchmark]
public void CallingTheSwitch2()
{
var result = SwitchOnValue2(new UserId("user-123"));
result = SwitchOnValue2(new ProductId("product-123"));
}

Once the benachmark program did its job, I was rewarded with a great confirmation:

| Method | Mean | Error | StdDev | Gen 0 | Allocated |
|------------------ |----------:|----------:|----------:|-------:|----------:|
| CallingTheSwitch1 | 4.977 ns | 0.1570 ns | 0.3172 ns | - | - |
| CallingTheSwitch2 | 20.872 ns | 0.4560 ns | 0.5068 ns | 0.0102 | 48 B |

The generic method will not do any boxing, T really is the struct and the fact that you used an interface for constraining the value is irrelevant. In the second case however, using the Interface requires a boxing operation of the struct with allocation and a significant performance penalty.