同志们,我简单介绍一下情况:
我正在编写一个小型可扩展应用程序。为了让用户添加自己的功能,在程序的框架内,我创建了他们必须继承其类型的接口
同时,我还在应用程序内部进行了代码生成,以提供更大的便利。
假设我们有一个这样的界面:
namespace MyAppNamespace
{
public interface INamed
{
string Name { get; }
}
}
此外,在应用程序内部生成以下代码(并放入resultCode):
using MyAppNamespace;
public class Wrapper : INamed
{
public string Name { get { return "Test"; } }
}
我正在编译这个:
// Указываю, что на выходе мне не нужен исполняемый файл, а также что сборку нужно создать по указанному пути
CompilerParameters options = new CompilerParameters { GenerateExecutable = false, GenerateInMemory = false, OutputAssembly = $"{SavePath}.dll" };
options.ReferencedAssemblies.Add(new Uri(GetType().Assembly.CodeBase, UriKind.Absolute).LocalPath); // Добавляю ссылку на текущую сборку для наследования интерфейса
// Получаю результат компиляции
CompilerResults results = new Microsoft.CSharp.CSharpCodeProvider().CompileAssemblyFromSource(options, resultCode);
// Опустим проверки
// Загружаю сборку из массива байт (так как сам файл потом, возможно, может быть удален)
Assembly asm = Assembly.Load(File.ReadAllBytes(results.PathToAssembly));
// Создаю instance типа, который в сборке унаследован от нужного интерфейса
INamed named = (INamed)Activator.CreateInstance(asm.DefinedTypes.First(x => x.ImplementedInterfaces.Contains(typeof(INamed))).AsType());
asm = null;
GC.Collect(); // Вычищаю сборку из памяти. По крайней мере, я хочу в это верить
return named;
之后,我可以安全地访问named.Name.
一段时间后,该对象被“丢弃”,直到用户明确表示他们想要使用它。在这种情况下,它需要从程序集中重新提取
但是有一个胖但是:如果我尝试以相同的方式加载程序集并再次从中获取类型,那么视觉上该过程将是成功的,但是当我尝试访问它时,named.Name会发生错误,即未找到定义它的程序集
我可以更正应用程序逻辑并一次性读取(也许它会更正确),但现在理解为什么会发生这种情况对我来说很重要:在第一次阅读时,一切都像发条一样工作,而在第二次阅读以相同的方式从同一个文件中,该过程是成功的,但是对象结果是“损坏”,因为当我尝试访问其属性时,我会收到一个错误,指出无法加载程序集
唉,这种信念是没有根据的。加载程序集的方式不仅不允许您在不卸载整个应用程序域的情况下从内存中卸载程序集,而且每次运行它时它还会再次从同一路径加载程序集(换句话说,这是一个当程序运行很长时间时内存不足的好方法)。
创建
Dictionary <string, Assembly>(其中字符串将是文件路径)并在其中缓存所有加载的程序集。如果您需要以某种方式考虑文件的内容,可以使用它来代替文件的路径 CRC/hash。或者将每个程序集加载到一个新的应用程序域中,然后可以将它们卸载(通常,这是创建带有扩展的应用程序时的常见做法)。