将位于不同目录中的程序集加载到新的 AppDomain






4.89/5 (9投票s)
如何将位于不同目录中的程序集加载到新的 AppDomain
正如一些人可能知道的,我一直在为我的 Cinch MVVM 框架开发一个代码生成器,我很高兴地说我快完成了。最后的障碍是,我需要从主代码引用的程序集中提取一堆命名空间,我想通过使用反射来实现,这非常容易。但我还希望将需要检查的程序集加载到新的 AppDomain
中,以便在我完成从程序集中反射命名空间后,可以卸载新创建的 AppDomain
。
经过多次失败的尝试和折腾之后,
Asembly.ReflectionOnlyLoad
AppDomain.Load(Byte[] rawAssembly)
- Fusion 路径
- 在我的 App.Config 中探测
AppDomain
Evidence 和AppDomainSetup
我终于找到了天堂,找到了最佳方案,如下所示
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Globalization;
using System.Security.Policy;
using System.Reflection;
using System.Diagnostics.CodeAnalysis;
namespace ConsoleApplication1
{
/// <summary>
/// Loads an assembly into a new AppDomain and obtains all the
/// namespaces in the loaded Assembly, which are returned as a
/// List. The new AppDomain is then Unloaded.
///
/// This class creates a new instance of a
/// <c>AssemblyLoader</c> class
/// which does the actual ReflectionOnly loading
/// of the Assembly into
/// the new AppDomain.
/// </summary>
public class SeperateAppDomainAssemblyLoader
{
#region Public Methods
/// <summary>
/// Loads an assembly into a new AppDomain and obtains all the
/// namespaces in the loaded Assembly, which are returned as a
/// List. The new AppDomain is then Unloaded
/// </summary>
/// <param name="assemblyLocation">The Assembly file
/// location</param>
/// <returns>A list of found namespaces</returns>
public List<String> LoadAssembly(FileInfo assemblyLocation)
{
List<String> namespaces = new List<String>();
if (string.IsNullOrEmpty(assemblyLocation.Directory.FullName))
{
throw new InvalidOperationException(
"Directory can't be null or empty.");
}
if (!Directory.Exists(assemblyLocation.Directory.FullName))
{
throw new InvalidOperationException(
string.Format(CultureInfo.CurrentCulture,
"Directory not found {0}",
assemblyLocation.Directory.FullName));
}
AppDomain childDomain = BuildChildDomain(
AppDomain.CurrentDomain);
try
{
Type loaderType = typeof(AssemblyLoader);
if (loaderType.Assembly != null)
{
var loader =
(AssemblyLoader)childDomain.
CreateInstanceFrom(
loaderType.Assembly.Location,
loaderType.FullName).Unwrap();
loader.LoadAssembly(
assemblyLocation.FullName);
namespaces =
loader.GetNamespaces(
assemblyLocation.Directory.FullName);
}
return namespaces;
}
finally
{
AppDomain.Unload(childDomain);
}
}
#endregion
#region Private Methods
/// <summary>
/// Creates a new AppDomain based on the parent AppDomains
/// Evidence and AppDomainSetup
/// </summary>
/// <param name="parentDomain">The parent AppDomain</param>
/// <returns>A newly created AppDomain</returns>
private AppDomain BuildChildDomain(AppDomain parentDomain)
{
Evidence evidence = new Evidence(parentDomain.Evidence);
AppDomainSetup setup = parentDomain.SetupInformation;
return AppDomain.CreateDomain("DiscoveryRegion",
evidence, setup);
}
#endregion
/// <summary>
/// Remotable AssemblyLoader, this class
/// inherits from <c>MarshalByRefObject</c>
/// to allow the CLR to marshall
/// this object by reference across
/// AppDomain boundaries
/// </summary>
class AssemblyLoader : MarshalByRefObject
{
#region Private/Internal Methods
/// <summary>
/// Gets namespaces for ReflectionOnly Loaded Assemblies
/// </summary>
/// <param name="path">The path to the Assembly</param>
/// <returns>A List of namespace strings</returns>
[SuppressMessage("Microsoft.Performance",
"CA1822:MarkMembersAsStatic")]
internal List<String> GetNamespaces(string path)
{
List<String> namespaces = new List<String>();
DirectoryInfo directory = new DirectoryInfo(path);
ResolveEventHandler resolveEventHandler =
(s,e)=> {
return OnReflectionOnlyResolve(
e, directory);
};
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve
+= resolveEventHandler;
Assembly reflectionOnlyAssembly =
AppDomain.CurrentDomain.
ReflectionOnlyGetAssemblies().First();
foreach (Type type in reflectionOnlyAssembly.GetTypes())
{
if (!namespaces.Contains(type.Namespace))
namespaces.Add(type.Namespace);
}
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve
-= resolveEventHandler;
return namespaces;
}
/// <summary>
/// Attempts ReflectionOnlyLoad of current
/// Assemblies dependants
/// </summary>
/// <param name="args">ReflectionOnlyAssemblyResolve
/// event args</param>
/// <param name="directory">The current Assemblies
/// Directory</param>
/// <returns>ReflectionOnlyLoadFrom loaded
/// dependant Assembly</returns>
private Assembly OnReflectionOnlyResolve(
ResolveEventArgs args, DirectoryInfo directory)
{
Assembly loadedAssembly =
AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies()
.FirstOrDefault(
asm => string.Equals(asm.FullName, args.Name,
StringComparison.OrdinalIgnoreCase));
if (loadedAssembly != null)
{
return loadedAssembly;
}
AssemblyName assemblyName =
new AssemblyName(args.Name);
string dependentAssemblyFilename =
Path.Combine(directory.FullName,
assemblyName.Name + ".dll");
if (File.Exists(dependentAssemblyFilename))
{
return Assembly.ReflectionOnlyLoadFrom(
dependentAssemblyFilename);
}
return Assembly.ReflectionOnlyLoad(args.Name);
}
/// <summary>
/// ReflectionOnlyLoad of single Assembly based on
/// the assemblyPath parameter
/// </summary>
/// <param name="assemblyPath">The path to the Assembly</param>
[SuppressMessage("Microsoft.Performance",
"CA1822:MarkMembersAsStatic")]
internal void LoadAssembly(String assemblyPath)
{
try
{
Assembly.ReflectionOnlyLoadFrom(assemblyPath);
}
catch (FileNotFoundException)
{
/* Continue loading assemblies even if an assembly
* can not be loaded in the new AppDomain. */
}
}
#endregion
}
}
}
我认为这段代码非常有用,希望你也能像我一样找到它的用处。我花了很长时间才弄清楚,进行了许多 Google 搜索,并咨询了 API/向朋友请教,才为你带来这段代码,所以请好好享受它。为了让它工作,我差点没累死。
像往常一样,这里有一个小演示应用程序