Well, I found something at least better than what I had in the above.
It uses named bindings after all, with ninject.extensions.conventions being used to name all bindings based on the last part of their namespace. This ends up attaching names to lots of bindings that don't need it (ones with only a single implementation available for the given interface), although attaching names to those bindings doesn't cause any problems in their use (at least in my testing).
If for some reason that were an issue for whoever runs across this in the future, you could just be more specific in the code that sets up the binding via conventions - for instance, only adding the named binding if the last namespace part were in a particular set or matched a particular pattern.
kernel.Bind(x => x
.FromThisAssembly()
.SelectAllClasses()
.BindAllInterfaces()
.Configure((binding, concreteType) =>
{
var concreteNamespace = concreteType.Namespace ?? String.Empty;
var lastNamespacePart = concreteNamespace.Split('.').Last();
binding.Named(lastNamespacePart);
})
);
It then uses the UseFirstArgumentAsNameInstanceProvider to look up the binding name based on the first parameter to the factory method (so you don't have to have separate methods for GetGen1, GetGen2, etc). I just changed the GetName override to do ToString since I was passing an enum instead of an actual string, but otherwise it's the same as from the linked wiki page.
public class UseFirstArgumentAsNameInstanceProvider : StandardInstanceProvider
{
protected override string GetName(System.Reflection.MethodInfo methodInfo, object[] arguments)
{
return arguments[0].ToString();
}
protected override ConstructorArgument[] GetConstructorArguments(System.Reflection.MethodInfo methodInfo, object[] arguments)
{
return base.GetConstructorArguments(methodInfo, arguments).Skip(1).ToArray();
}
}
I'm going to leave the question open for awhile just in case there's a different/better option available, but at least this seems reasonable and doesn't have any obvious OCP problems. :)