我正在帮助开发的系统的一个要求是能够导入文件,我们有一组适配器来处理我们期望遇到的不同类型的文件(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
的第二个版本会崩溃,看起来像是一个完全不相关的代码部分?如果我们添加足够的数据适配器会导致代码崩溃吗?
答案 0 :(得分:1)
此异常指出未找到所需的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扫描。
答案 1 :(得分:1)
当我们编写一个从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
的文件,这些都不是你所期望的。只是为了捕获异常并忽略损坏的文件而不是试图预先确定哪些是受管理的dll而哪些不是。