我正在帮助开发的系统的一个需求是能够导入文件,我们有一组适配器来处理不同类型的文件(csv,xml等)。我们预计会遇到。在开发的早期阶段,我们通过引用和使用命令对数据适配器进行硬编码。显然,当它上线时,我们想要的情况是,我们可以只编写一个新的适配器,并将dll放入文件夹中,然后在不重新编译代码的情况下运行过程。
为了实现这一点,我改编了this question中的代码。有问题的代码位于构造函数中,如下所示
string dllLocation = @"C:MyLocation\dllLocation";
DirectoryInfo dir = new DirectoryInfo(dllLocation);
var tempfiles = dir.GetFiles("*Adapter*.dll", SearchOption.AllDirectories); // This will need to be changed when we go live
foreach (var file in tempfiles)
{
Assembly tempAssembly = null;
//Before loading the assembly, check all current loaded assemblies in case already loaded
//has already been loaded as a reference to another assembly
//Loading the assembly twice can cause major issues
foreach (Assembly loadedAssembly in AppDomain.CurrentDomain.GetAssemblies())
{
//Check the assembly is not dynamically generated as we are not interested in these
if (loadedAssembly.ManifestModule.GetType().Namespace != "System.Reflection.Emit")
{
//Get the loaded assembly filename
string loadedFilename = loadedAssembly.CodeBase.Substring(loadedAssembly.CodeBase.LastIndexOf('/') + 1);
//If the filenames match, set the assembly to the one that is already loaded
if (loadedFilename.ToUpper() == file.Name.ToUpper())
{
tempAssembly = loadedAssembly;
break;
}
}
}
//If the assembly is not aleady loaded, load it manually
if (tempAssembly == null)
{
tempAssembly = Assembly.LoadFrom(file.FullName);
}
Assembly a = tempAssembly;稍后,当该方法运行时,我们将获得以下内容
private IEnumerable<IUniversalDataAdapter> DataAdapters
{
get
{
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
foreach (var type in asm.GetTypes().Where(x => x.GetInterfaces().Contains(typeof(IUniversalDataAdapter))))
{
if (type.IsAbstract) continue; // can't create abstract classes
if (!dataAdapters.Any(y => y.GetType().Equals(type)))
{
IUniversalDataAdapter adapter = (IUniversalDataAdapter)Activator.CreateInstance(type);
dataAdapters.Add(adapter);
}
}
}
return dataAdapters;
}
}它成功地加载了数据适配器,并允许按照我所期望的那样使用它们。
现在问题来了,在第一段代码中,我们有一行
var tempfiles = dir.GetFiles("*Adapter*.dll", SearchOption.AllDirectories);如果我将其更改为
var tempfiles = dir.GetFiles("*.dll", SearchOption.AllDirectories);在此例程运行第二段代码之前,例程崩溃
private IEnumerable<IDataValidator> DataValidators
{
get
{
if (validators.Count == 0)
{
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
foreach (var type in asm.GetTypes().Where(x => x.GetInterfaces().Contains(typeof(IDataValidator))))
{
if (!type.IsAbstract)
{
var validator = (IDataValidator)Activator.CreateInstance(type, context);
validators.Add(validator);
}
}
}
}
return validators;
}
}编辑:添加异常
System.Reflection.ReflectionTypeLoadException was unhandled
HResult=-2146232830
Message=Unable to load one or more of the requested types. Retrieve the LoaderExceptions property for more information.
Source=mscorlib
StackTrace:
at System.Reflection.RuntimeModule.GetTypes(RuntimeModule module)
at System.Reflection.RuntimeModule.GetTypes()
at System.Reflection.Assembly.GetTypes()
at TTi.Data.Pipeline.Server.Common.DataPipeline.get_DataValidators() in C:\Users\anorcross\Source\Workspaces\Universal System\Data\Main\TTi.Data\TTi.Data.Pipeline.Server.Common\DataPipeline.cs:line 124
at TTi.Data.Pipeline.Server.Common.DataPipeline.CheckConfiguration(DataConfiguration config) in C:\Users\anorcross\Source\Workspaces\Universal System\Data\Main\TTi.Data\TTi.Data.Pipeline.Server.Common\DataPipeline.cs:line 528
at TTi.Data.Pipeline.Server.Common.DataPipeline.ProcessDataSource(IDataSource dataSource, DataConfiguration config) in C:\Users\anorcross\Source\Workspaces\Universal System\Data\Main\TTi.Data\TTi.Data.Pipeline.Server.Common\DataPipeline.cs:line 213
at TTi.Data.Test.Program.ImportTest(String testFolders) in C:\Users\anorcross\Source\Workspaces\Universal System\Data\Main\TTi.Data\TTi.Data.Test\Program.cs:line 362
at TTi.Data.Test.Program.Main(String[] args) in C:\Users\anorcross\Source\Workspaces\Universal System\Data\Main\TTi.Data\TTi.Data.Test\Program.cs:line 48
at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
InnerException: EndEdit:
DataValidators绝对会在DataAdapters例程之前运行。DataValidators只是包含在主代码库中的几位代码,并进行检查以确保导入的数据具有预期的格式。在这一点上,我们只是加载它们,这样我们就可以确保所需的文件存在。
查看加载的程序集,两个版本的代码都根据需要加载适配器,但第二个版本的加载比第一个版本更多,正如我所预期的那样。
那么,为什么第二个版本的tempfiles会在看起来完全不相关的代码部分崩溃呢?如果我们添加了足够多的数据适配器,会导致代码崩溃吗?
发布于 2017-02-01 17:45:18
此异常说明找不到所需的dll,因此无法加载它。现在,他正在寻找哪个dll,为什么它在编译时没有失败?要回答第一个问题,您需要更深入地研究,最终您会发现您的代码加载了一些您不使用或不需要的奇怪的dll。这可能是因为代码迭代了所有加载的程序集中的所有类型。可以有一个dll作为引用,该dll公开使用您未引用其他dll中的某些类型的类型。只要你不使用那个类型就可以了。
为了解决这个问题,让你的循环更具体。很明显,您的适配器不会在任何系统dll中声明,那么为什么要迭代这些dll呢?
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies().Where(IsMyAssembly))
{
foreach (var type in asm.GetTypes().Where(x => x.GetInterfaces().Contains(typeof(IDataValidator))))
{
//...使用Adapter的第一次文件扫描比没有使用它的文件扫描更好,因为它更具体。我建议让你的代码更具体,例如在一些配置文件中显式注册类型,这将完全消除dll扫描。
发布于 2017-02-01 17:46:32
当我们编写一个从dll获取所有匹配类型的函数时,我们遇到了完全相同的问题。请注意try catch。
private static IEnumerable<Type> MatchingTypesFromDll<TBaseType>(string dllPath)
{
try
{
return Assembly
.LoadFrom(dllPath)
.GetExportedTypes()
.Where(TypeSatisfies<TBaseType>);
}
catch (Exception e)
{
Debug.WriteLine($"Exception {e} when trying to load from {dllPath}");
return new Type[] {};
}
}我们只是忽略了异常。即使在nuget包中,也有一些原生dll或可能名为.dll的文件并不是您所期望的。仅仅捕获异常并忽略损坏的文件要比试图抢先找出哪些是托管的dlls,哪些不是。
https://stackoverflow.com/questions/41975873
复制相似问题