We spent some time dealing with it.
public class CustomAssemblyResolver : BaseAssemblyResolver, IDisposable
{
readonly IDictionary<string, AssemblyDefinition> cache;
private readonly List<AssemblyDefinition> resolved = new
List<AssemblyDefinition>();
public CustomAssemblyResolver()
{
cache = new Dictionary<string, AssemblyDefinition>();
}
public override AssemblyDefinition Resolve(AssemblyNameReference name)
{
if (name == null)
throw new ArgumentNullException("name");
AssemblyDefinition assembly;
if (cache.TryGetValue(name.FullName, out assembly))
return assembly;
assembly = base.Resolve(name);
cache[name.FullName] = assembly;
resolved.Add(assembly);
return assembly;
}
public override AssemblyDefinition Resolve(string name)
{
var assembly = base.Resolve(name);
resolved.Add(assembly);
return assembly;
}
public override AssemblyDefinition
Resolve(AssemblyNameReference name, ReaderParameters parameters)
{
var assembly = base.Resolve(name, parameters);
resolved.Add(assembly);
return assembly;
}
public override AssemblyDefinition Resolve(string fullName,
ReaderParameters parameters)
{
var assembly = base.Resolve(fullName, parameters);
resolved.Add(assembly);
return assembly;
}
protected void RegisterAssembly(AssemblyDefinition assembly)
{
resolved.Add(assembly);
if (assembly == null)
throw new ArgumentNullException("assembly");
var name = assembly.Name.FullName;
if (cache.ContainsKey(name))
return;
cache[name] = assembly;
}
public void Dispose()
{
resolved.ForEach(q => q.Dispose());
}
public string GetName()
{
return this.GetSearchDirectories()[0];
}
public IEnumerable<string> GetCachedAssemblies()
{
return resolved.Select(item => item.FullName);
}
public void SetNewSearchDirectoryTo(string path)
{
var dirs = GetSearchDirectories();
foreach (var d in dirs)
{
RemoveSearchDirectory(d);
}
AddSearchDirectory(path);
}
}
On Wed, Jan 18, 2012 at 5:43 PM, Ken Beckett <[email protected]> wrote:
> I have an app that loads lots of .NET assemblies using Mono Cecil
> (actually a VS Solution with all Projects and all assembly
> references), and then repeats this process ad infinitum.
>
> There seems to be a terrible memory leak in the design of Mono Cecil
> when doing such a thing, in that the DefaultAssemblyResolver class
> keeps a private cache of all loaded AssemblyDefinition objects, and
> yet provides no means of ever purging this cache. That sort of thing
> is always a nasty thing to do. The simple addition of a PurgeCache()
> method would be nice - otherwise it's necessary to create a custom
> AssemblyResolver that derives from BaseAssemblyResolver to get around
> this problem.
>
> Which actually brings up another issue - the way that
> BaseAssemblyResolver lets you add/remove search directories is sort of
> nice... except that you never should actually remove a search
> directory due to deferred resolving that can occur, and therefore a
> single instance of this class should never be used to load assemblies
> for separate Projects or executables since they will generally need
> different search paths. This problem therefore extends to the
> DefaultAssemblyResolver and GlobalAssemblyResolver.Instance - giving
> them not proper behavior for repeated loads of different groups of
> assemblies using different search paths.
>
> In summary, Mono Cecil appears to be designed to do a one-time load of
> a group of related assemblies that you never unload from memory. It
> does not appear to handle (as-is) repeated loads of different groups
> of assemblies with different search paths and proper unloading. This
> would seem to require a custom AssemblyResolver with different
> instances (and search paths) used for each related group that is
> loaded, and any AssemblyDefinition caching must be done at a higher/
> separate level in order to work properly.
>
> --
> --
> mono-cecil
--
Le doute n'est pas une condition agréable, mais la certitude est absurde.
--
--
mono-cecil