Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Iterate through implementations of an interface and invoke a method

Tags:

c#

.net

Let's say I have several implementations of an interface:

public interface IDemoInterface
{
    void DemoMethod();
}

public class DemoClass1 : IDemoInterface
{
    public void DemoMethod()
    {
        Console.WriteLine("DemoClass1");
    }

}

public class DemoClass2 : IDemoInterface
{
    public void DemoMethod()
    {
        Console.WriteLine("DemoClass2");
    }

}

Now, I am trying to define an array of such implementations and for each implementation call the interface method. Essentially, do something like this (what follows is a NON-WORKING code -- just an illustration of what I am trying to do):

public static void Run()
{
    var demoClasses = { DemoClass1, DemoClass2};

    foreach (var demoClass in demoClasses)
    {
        var implementation = demoClass as IDemoInterface;
        implementation.DemoMethod();
    }
}

What is the best way to accomplish it? Is using Reflection the only way?

like image 208
July.Tech Avatar asked Dec 14 '22 18:12

July.Tech


1 Answers

From your example, it looks like you're not trying to loop through instances of classes that implement your interface. You're trying to loop through the types themselves. I'm basing it on this...

var demoClasses = { DemoClass1, DemoClass2};

...and in your question DemoClass and DemoClass2 are class types, not instances of types.

You could start with a collection of types and then create an instance of each type, like this. (You could also use reflection to find the class types that implement your interface.)

var types = new Type[] {typeof(DemoClass1), typeof(DemoClass1)};
foreach (var type in types)
{
    if (typeof(IDemoInterface).IsAssignableFrom(type))
    {
        var instance = Activator.CreateInstance(type) as IDemoInterface;
        instance.DemoMethod();
    }
}

That will work, but there's a huge limitation: Each class must have a default constructor. Or if you're going to call a constructor, they would all need to have the same one.

In other words, if one of these classes has a constructor like this:

public DemoClass1(string connectionString){

then Activator.CreateInstance will blow up unless you give it the values to pass into the constructor. But if you have an array of types and they have different constructors then it's pretty much impossible to do that. And you don't want to paint yourself into a corner where you have to write classes that can only have empty constructors.

When you have an interface and classes that implement that interfaces, and you want to get instances of those classes, a dependency injection container (Ioc container) can be helpful. If you haven't used it there's a tiny bit of a learning curve, but it's a very useful tool to have in your box. Here's an example (this is from my blog) of configuring a Windsor container to "register", or declare several implementations of an interface.

(If you're not familiar with this it's going to seem totally out of nowhere, which is why I also linked to Windsor's documentation.)

public class DependencyRegistration : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Kernel.Resolver.AddSubResolver(new CollectionResolver(container.Kernel));
        container.Register(Component.For<OrderValidator>());

        container.Register(
            Component.For<IOrderValidator,AddressValidator>()
                .Named("AddressValidator"),
            Component.For<IOrderValidator,OrderLinesValidator>()
                .Named("OrderLinesValidator"),
            Component.For<IOrderValidator,ProductStateRestrictionsValidator>()
                .Named("ProductStateRestrictionsValidator")
            );
    }
}

In this example I've told this "container" that there are three classes that implement IOrderValidator.

Now this container can "resolve", or return, an array of implementations of IOrderValidator. Notice this line near the top:

container.Register(Component.For<OrderValidator>());

Here's that class:

public class OrderValidator : IOrderValidator
{
    private readonly IOrderValidator[] _subValidators;

    public OrderValidator(IOrderValidator[] subValidators)
    {
        _subValidators = subValidators;
    }

    public IEnumerable<ValidationMessage> GetValidationMessages(Order order)
    {
        var validationMessages = new List<ValidationMessage>();
        foreach(var validator in _subValidators)
        {
            validationMessages.AddRange(validator.GetValidationMessages(order));
        }
        return validationMessages;
    }
}

Notice that in the constructor there is an array of IOrderValidator. If I were to call

var validator = container.Resolve<OrderValidator>();

the container would create an instance of OrderValidator. It would detect that the constructor requires an array of IOrderValidator, so it would create instances of all of those other classes and place them in the array.

If any of those classes also had constructors that required other values or classes, and I told the container how to create those, it would create those as needed in order to be able to create the implementations of IOrderValidator.

The result is that I can have numerous implementations of a class, each with different constructor dependencies of their own, and I can create a collection of those implementations.

This answer doesn't go far toward really telling you how to do this, but hopefully it shows where to find the tools to accomplish this sort of thing. DI containers are very useful tools when used correctly. It's common practice in large applications and even small ones because it makes it easier to manage lots and lots of class types that may be nested inside one another while keeping it sane and understandable. It also helps us to write classes that are smaller and easier to unit test.

In addition to Windsor some other containers are Autofac, Unity, SimpleInjector, and the DI container that's included with ASP.NET Core applications.

like image 118
Scott Hannen Avatar answered Mar 08 '23 23:03

Scott Hannen