I have a prototype where I intentionally invoke GC after every frame. My reasoning is that some amount of GC will eventually need to occur, so why not do it as frequently as possible and at the exact moment where we can compensate with other measures, such as the frame scheduler & time step abstractions.
Even with heavy allocation (i.e. deserializing the scene graph from a wire protocol before every frame), this approach seems to keep GC pauses bounded to ~1-5 milliseconds in my implementations.
If I did not explicitly call GC when I thought best, the runtime would decide to run a 100-1000 ms collection at its convenience and the entire experience would become worthless to human perception.
Missing a VSync in mobile means that the refresh rate will switch to 30hz for a brief moment, there is no in-between (ie; 54 FPS or something like that) which is pretty noticeable.
The only "great strategy" is to have zero alloc (or close) while gameplay is running and if you want to go the extra miles, disable GC completely and call it only in some appropriate moment (level loading, bringing up the menu, etc.).
This is the path I am taking.
Ultimately, I would try as hard as I can to not allocate, but having a good plan for dealing with some per-frame blips is going to be essential. It gives me a reasonable starting point to work with on the journey to zero alloc.
But it's not very complicated to write a simple IL analyzer, using Mono.Cecil to go over the raw IL, instead of using Roslyn. The idea is to look for the newobj or box IL opcodes, but also to recurse to called methods (when meeting the callX opcodes), by finding using Cecil where the call leads us, and loading that method.
This could be used after compilation instead of during compilation, but it can be used without actually running the code, so it's a sort of static analysis.
I threw together a simple demo, that has some bugs but is generic enough and should work. It's not very efficient though.
See here :
https://dotnetfiddle.net/EyuYSh
(It can also be run from there since dotnetfiddle supports nugets).
And also in godbolt:
https://godbolt.org/z/P3rK1voKP
(it doesn't run there since Godbolt apparently doesn't support nugets)
It's self contained and will run as a console application by just copying and pasting the code into an IDE. You need to add Mono.Cecil as a nuget package though.