在Unity中获取实现某个接口的所有类型

4
如果您只想知道解决方案,请跳到“更新”部分:
我有一个应用程序,使用以下代码获取并运行许多工作方法。
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();
}

这段代码目前可以完美运行。然而,一些最新的工作利用依赖注入来填充它们的构造函数,因此这种方法将来将不可行。所以我想知道是否有一种使用Unity的方法来解决这个问题?
我的原始想法是继续使用前半部分,然后用resolve替换foreach逻辑,使其看起来像以下内容。
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();
}

问题在于,一旦我定义了UnityContainer,实现IJob的返回类型列表就会突然膨胀,显示出所有这些垃圾Microsoft.Practices类,如下所示。
更新:
事实证明,当反射程序集时,如果存在Unity,则将尝试反射到Unity的程序集中,如果使用ToList完成,则由于缺少IServiceLocator的元数据扩展而抛出异常。为解决此问题,在GetAssemblies()后附加一个where子句,以限制范围为您想要的命名空间,即可使应用程序正常运行。
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
我不确定你如何在types列表中获取接口 - 你能否请澄清一下你所说的“我的类型返回”是什么? - Alexei Levenkov
当然。我基本上想要提取所有实现IJob接口的类型,然后调用它们的run方法。问题是,由于某种原因,当我尝试使用容器进行此操作时,返回的var类型会膨胀成一堆不实现IJob接口的Microsoft.Practices.Unity类型引用。说实话,我不明白这是如何可能的。 - Jon Gear
1
请问您能展示一下 types.ToList().Count() 的结果吗?我怀疑您发布的截图并不是您正在寻找的列表... - Alexei Levenkov
我认为你可能有所发现。当我在toList上进行操作时,导致我的查询最终化,VS抛出了以下错误--无法加载一个或多个请求的类型。检索LoaderExceptions属性以获取更多信息。-------------------------------- > {"无法加载文件或程序集'Microsoft.Practices.ServiceLocation,Version=1.2.0.0,Culture=neutral,PublicKeyToken=31bf3856ad364e35'或其某个依赖项。系统找不到指定的文件。":"Microsoft.Practices.ServiceLocation,Version=1.2.0.0,Culture=neutral,PublicKeyToken=31bf3856ad364e35"} - Jon Gear
1
看来是这样 - 你正在查看“Where”的预过滤内部列表,它确实显示了所有类型。正如有些人所预期的那样,你遇到了加载某些类型的问题 - 你真的应该放弃Linq,并手动迭代每个程序集/类型,并在每个周围使用小心的try/catch代码。 - Alexei Levenkov
非常正确。基本上,当反射Type .net时,会拉入Unity的IServiceLocator实现,这就是导致我的应用程序出现问题的原因...https://dev59.com/IGLVa4cB1Zd3GeqPwGyg......那篇文章解释了这一点。感谢您的帮助! - Jon Gear
3个回答

2

通过自定义属性来过滤程序集,而不是搜索所有程序集。这样可以大大缩小搜索范围。

以下是创建自定义程序集级别属性的方法:

自定义程序集属性


1
请注意,OP声称x => x.IsClass不能过滤接口...因此,虽然仅查看已选择的程序集/类确实是非常好的解决方案,但可能对OP没有帮助... - Alexei Levenkov
你们两个都是正确的。我不知道你可以创建自定义程序集属性,但遗憾的是它并不能解决我的问题,因为Unity在我的引用程序集中,但它确实有助于限制反射的程序集。 - Jon Gear

1
在Unity中,有几件事情需要注意才能使其正常工作:
  1. 您需要为每个实例注册不同的名称。未命名的实例无法解析为数组或。
  2. InjectionConstructorResolvedArrayParameter内部的注册期间必须显式调用ResolveAll方法。
这里是一个演示应用程序:
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;
        }
    }
}

是的,在注册同一接口的多个实现程序时,传递一个名称允许您稍后调用unityContainer.ResolveAll<ISomething>()并返回所有实现程序。如果在注册时不指定名称,则从此方法中获取的是空列表。 - Roboblob

0

这是我的贡献,伙计们:

//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);
        });
});

jobFile变量是指向XML文件的路径,该文件包含了计划/作业/触发器等内容。 - 26a

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接