The primary issue with your code sample is the calling of the MainForm from your Main thread. After dusting off my constructor injection knowledge (I don't use it terribly much) it looks like you do have a good constructor injection model in place.
if (Settings.GlobalSettings.LoginSuccess == true)
{
Application.Run(new MainForm( new CatagoryRepository(),
new ExamineeRepository(),
new ExamResultRepository(),
new QuestionRepository(),
new TestRepository(),
new UserRepository(),
new UserTestDetailRepository()
));
}
This is problematic because you are passing set, concrete types to the constructor and therefore not making use of the DI pattern.
From a purely functional level you do have a Dependency Injected object based on the passing of static parameters (that implement an interface) to a constructor that expects interface types rather than classes. When people talk about DI, though, there's generally another inferred component, which is the Object Resolver. This is where the capability of DI really comes into play.
When you develop a DI application, the intent is that your dependencies should be easily changed out, but doing so without an object resolver generally defeats the purpose, since you will still need to find every function call and swap out your parameters.
So you set a resolver that provides the appropriate type based on application configuration each time it is requested. This is where I wrote half of an operational framework before I realized that I was going too far and getting into the weeds.
Either using an existing framework, or rolling your own, you declare a resolver and use it throughout the application to instantiate your types. I'm using Ninject here for simplicity.
public class NinjectBindings : NinjectModule
{
public override void Load()
{
Bind<ICatagoryRepository>.To<CatagoryRepository>();
....
Bind<IUserTestDetailRepository>.To<UserTestDetailRepository>();
}
}
static void Main()
{
IKernel kernel = new StandardKernel(new NinjectBindings());
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(kernel.Get<FrmLogin>());
if (Settings.GlobalSettings.LoginSuccess == true)
{
Application.Run(kernel.Get<MainForm>());
}
}
Now if you remove the default constructor from MainForm it will be assembled using the bound items, as will all objects called using kernel.Get<type>(). You can set this up as a static resolver if you like, or pass it through your application, but the key is that you now have a single point where changes need to be made and consistency will be maintained in terms of type safety.
Your example will work fine for constructor injection (apart from the default constructor. If you won't always have all interfaces implemented, you can use property injection instead to provide flexibility at the cost of a little null-checking code.