Get All Types That Implement An Interface In Unity

2020-07-17 08:35发布

问题:

Please skip to the UPDATE if you would like to just know the solution:

I have an application that uses the following code to get and run a number of worker methods

var type = typeof(IJob);
var types = AppDomain.CurrentDomain.GetAssemblies()
                .SelectMany(x => x.GetTypes())
                .Where(x => x.IsClass && type.IsAssignableFrom(x));

foreach (Type t in types)
{
    IJob obj = Activator.CreateInstance(t) as IJob;
    obj.Run();
}

This code works perfectly as is. However, some of the newer jobs utilize dependency injection to populate their constructors so this method will not be viable going forward. So I was wondering if there's a way to do this with unity?

My original thought was that I would continue with the first half and then replace the foreach logic with resolve so that it looks something like the following.

var type = typeof(IJob);
var types = AppDomain.CurrentDomain.GetAssemblies()
                .SelectMany(x => x.GetTypes())
                .Where(x => x.IsClass && type.IsAssignableFrom(x));

foreach (Type t in types)
{
    IJob obj = Container.Resolve(t) as IJob;
    obj.Run();
}

The problem is that as soon as I define my UnityContainer the returned types list that implement IJob suddenly gets bloated with all of these garbage Microsoft.Practices classes as shown below

UPDATE:

It turns out then when refelecting over Assemblies if Unity is present it will attempt to reflect into Unity's assemblies which if Finalized with a ToList will throw an exception due to a missing metadata extension of IServiceLocator. To work around this appending a where clause after GetAssemblies() to limit scope to your desired namespace will allow the application to run properly.

var type = typeof(IJob);
var types = AppDomain.CurrentDomain.GetAssemblies()
                .Where(x => x.FullName.StartsWith("YourNamespace"))
                .SelectMany(x => x.GetTypes())
                .Where(x => x.IsClass && type.IsAssignableFrom(x));

foreach (Type t in types)
{
    IJob obj = Container.Resolve(t) as IJob;
    obj.Run();
}

回答1:

Instead of searching through all assemblies, filter them by a custom attribute. This way you narrow the searching dramatically.

This is how to create a custom assembly level attribute

Custom Assembly Attributes



回答2:

In Unity, there are a couple of things you need to take care of to get this working:

  1. You need to register each instance with a different name. Unnamed instances cannot be resolved as an array or IEnumerable<T>.
  2. You have to call the ResolveAll method explicitly during registration inside of an InjectionConstructor and ResolvedArrayParameter.

Here is a demo application:

using Microsoft.Practices.Unity;
using System;
using System.Collections.Generic;
using System.Linq;

namespace UnityExperiment
{
    class Program
    {
        static void Main(string[] args)
        {
            // Begin composition root
            var container = new UnityContainer();
            container.AddNewExtension<JobContainerExtension>();
            container.RegisterType<IService1, Service1>(new InjectionConstructor(
                new ResolvedArrayParameter<IJob>(container.ResolveAll<IJob>().ToArray())));
            container.RegisterType<IService2, Service2>(new InjectionConstructor(
                new ResolvedArrayParameter<IJob>(container.ResolveAll<IJob>().ToArray())));
            // End composition root


            var service1 = container.Resolve<IService1>();
            var service2 = container.Resolve<IService2>();
        }
    }

    public class JobContainerExtension : UnityContainerExtension
    {
        protected override void Initialize()
        {
            var interfaceType = typeof(IJob);
            var implementationTypes = AppDomain.CurrentDomain.GetAssemblies()
                            .Where(x => x.FullName.StartsWith("UnityExperiment"))
                            .SelectMany(x => x.GetTypes())
                            .Where(x => x.IsClass && interfaceType.IsAssignableFrom(x));

            foreach (Type implementationType in implementationTypes)
            {
                // IMPORTANT: Give each instance a name, or else Unity won't be able
                // to resolve the collection.
                this.Container.RegisterType(interfaceType, implementationType, 
                    implementationType.Name, new ContainerControlledLifetimeManager());
            }
        }
    }

    public interface IJob
    {
    }

    public class Job1 : IJob
    {
    }

    public class Job2 : IJob
    {
    }

    public class Job3 : IJob
    {
    }

    public interface IService1
    {
    }

    public class Service1 : IService1
    {
        private readonly IJob[] jobs;

        public Service1(IJob[] jobs)
        {
            this.jobs = jobs;
        }
    }

    public interface IService2
    {
    }

    public class Service2 : IService2
    {
        private readonly IEnumerable<IJob> jobs;

        public Service2(IEnumerable<IJob> jobs)
        {
            this.jobs = jobs;
        }
    }
}


回答3:

Here is my contribution fellas:

//Register all IJob implementations that are not generic, abstract nor decorators
Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "SomeFilter*.dll")
.Select(file => Assembly.LoadFile(file))
.ForEach(s =>
{
    s.GetTypes()
        .Where(type => typeof(IJob).IsAssignableFrom(type) && (!type.IsAbstract && !type.IsGenericTypeDefinition))
        .Select(type => new { type, ctor = type.GetConstructors().Any(ct => ct.GetParameters().Any(p => p.ParameterType == typeof(IJob))) == false })
        .Select(type => type.type)
        .ForEach<Type>(o =>
        {
            string jobFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, string.Format("{0}.xml", Path.GetFileNameWithoutExtension(o.Assembly.Location)));
            var typeLoadHelper = new SimpleTypeLoadHelper();
            typeLoadHelper.Initialize();
            XMLSchedulingDataProcessor processor = new XMLSchedulingDataProcessor(typeLoadHelper);
            processor.AddJobGroupToNeverDelete("XMLSchedulingDataProcessorPlugin");
            processor.AddTriggerGroupToNeverDelete("XMLSchedulingDataProcessorPlugin");
            processor.ProcessFileAndScheduleJobs(jobFile, jobFile, this.Scheduler);
        });
});