I really recommend a pragmatic approach here.
1) Design an abstraction for "your dream IoC container", with only the bare minimum that you need. Something like this:
public interface IContainer
{
void RegisterType<TSource, TDestination>();
void RegisterType<TSource>(Func<TSource, TDestination> generator);
T Resolve<T>();
}
2) Create an implementation of your abstraction that simply delegates all the functionality to an existing component. I recommend Autofac but there's plenty of fish in the sea.
3) Develop your application using your "wrapper IoC".
4) If at some point you find that the external IoC component has performance issues (or any other type of issues), write another implementation of your IoC abstraction that uses another external component, your own code, or a combination of both. Even if your application is in an advanced state, you just have to change the small piece of code that instantiates the IoC wrapper (perhaps just one line of code).
Advantages of this approach:
- You use a mature and well-tested IoC container (if you choose wisely) while hiding its complexity behind a small interface. This helps improve code readability.
- You don't fall in the premature optimization trap.
- You can entirely switch from one IoC container to another one with very little impact in your existing code (if your abstraction is well designed). This wipes out (or at least minimizes) the "using libraries that I don't control" concern.
Of course, you will have to make the abstraction grow as you need more advanced functionality. But you should always start with a simple abstraction.
At my work place we are using a more elaborate version of this approach and it's working nicely.