note, my examples below are for Revit, but the ideas should hold true for Civil3d
The only solution I have seen similar to that is how archilab is released and that creates a ton of issues if you are managing deployments for anyone on multiple versions of the host software.
For my package Rhythm, I elected to separate functionality for later versions and use invoke to fire the commands off right from the DLL. This means the dependencies (for Revit 2022 in my case) don;t actually get referenced in older Revit versions.
Here is sample code:
The Rhythm Revit 2022 code (which becomes its own DLL RhythmRevit2022.dll in the extra folder):
and the actual ZeroTouch code that calls it (in my base Rhythm.dll):