With the .NET SDK, you only need to call AuthenticateAsync() once for an API object. It then stores that information internally and applies it to subsequent API calls that it makes. It would also need to re-authenticate once the internal session expires, but as you say that should happen only a few times per month (per object). You also don't need to manually refresh expired sessions - it will handle that internally too if it was constructed with a username and password.
EDIT: Technically you don't need to explicitly call AuthenticateAsync() on the API object. It validates credentials before making other calls and will make the authenticate call itself beforehand with the credentials it was given on construction. We've found that can cause race conditions when multiple threads try to make calls on startup though, so we always ensure an explicit Authenticate is performed before we allow other calls.
Also, after inspecting the API object further, it will also automatically try to refresh expired sessions if it was initially constructed with a username and password. When it makes a call that throws an InvalidUserException, it clears out the credentials then retries the entire call, which prompts it to re-authenticate. That's obviously not possible if the API object only had a session token to begin with, so for API objects constructed from sessions, those would need to be manually recreated / refreshed on expiration.