Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Loading Assembly to Leave Assembly File Unlocked

4.91/5 (8 votes)
2 Nov 2014CPOL2 min read 41.7K  
How to load assembly into memory in .NET application to have assembly file unlocked

Problem

If you need to manipulate .NET assembly files (rename, move, etc.) and at the same time load assembly in memory, you may be unpleasantly surprised by the fact that assembly file becomes locked by your application.

Using:

C#
Assembly.LoadFrom(assemblyPath);

or:

C#
Assembly.ReflectionOnlyLoadFrom(assemblyPath);

causes the assembly file to be locked.

I faced this problem creating an Assembly Strong Name Sign Tool when I needed to read assembly information and then rename assembly file.

Thanks to the CodeProject community feedback to this tip, I have found the 3 following solutions.

Solutions

  1. Using Assembly.Load(Byte[]) method
    (Proposed by SpArtA)
    C#
    Assembly.Load(File.ReadAllBytes(assemblyPath));

    In this way, assembly is loaded from new byte array in memory and no actual file lock is made.

    Pros: The simplest solution, requires a single line of code.
    Cons: The assembly location is unknown. You can't use Assembly.Codebase and Assembly.Location

  2. Loading assembly in a separate AppDomain
    (Also described in Sacha Barber's Blog in 2009)

    The only way to unload an assembly from an application domain is by unloading the Application Domain
    Application Domain is a unit of isolation in .NET that is like a subprocess in the process of application.

    The solution is to create a temporary AppDomain, read assembly data in it and then unload the temporary domain from memory.

    First, we need to create a class that holds assembly information.
    Class must be marked as [Serializable] since it travels between domain boundaries.

    C#
    [Serializable]
    public class AssemblyInfo
    {
        public string Name { get; set; }
        public string Version { get; set; }
        public string RuntimeVersion { get; set; }
        public string Platform { get; set; }
    
        public AssemblyInfo()
        {
        }
    
        public AssemblyInfo(AssemblyName assemblyName)
        {
            Name = assemblyName.Name;
            Version = assemblyName.Version.ToString();
            RuntimeVersion = "";
            Platform = assemblyName.ProcessorArchitecture.ToString();
        }
    
        public AssemblyInfo(Assembly assembly)
            : this(assembly.GetName())
        {
            RuntimeVersion = assembly.ImageRuntimeVersion;
        }
    }

    Then, we need to create a proxy class that will do stuff in another domain and return data to the current domain.
    It must inherit from MarshalByRefObject.
    Only classes inherited from MarshalByRefObject can be accessed between different application domain boundaries.

    C#
    public class AssemblyLoader : MarshalByRefObject
    {
        public AssemblyInfo LoadAssemlyInfo(string assemblyPath)
        {
            Assembly assembly = Assembly.ReflectionOnlyLoadFrom(assemblyPath);
            return new AssemblyInfo(assembly);
        }
    }

    Then, you create a temporary app domain, load the assembly in it and unload the domain:

    C#
    public static AssemblyInfo GetAssemblyInfo(string assemblyPath, bool lite = false)
    {
        AssemblyInfo assemblyInfo = null;
    
        // create a temporary app domain
        AppDomain tempDomain = AppDomain.CreateDomain("TempDomain");
    
        // create proxy instance in temporary domain
        var asmLoader = (AssemblyLoader)tempDomain.CreateInstanceAndUnwrap(
            typeof(AssemblyLoader).Assembly.FullName, typeof(AssemblyLoader).FullName);
    
        // load assembly in other domain
        assemblyInfo = asmLoader.LoadAssemlyInfo(assemblyPath);
    
        // unload temporary domain and free assembly resources
        AppDomain.Unload(tempDomain);
    
        return assemblyInfo;
    }

    Pros: You have access to full assembly info including Assembly.Codebase and Assembly.Location
    Cons: Method requires much more code including 3 separate classes.

  3. Configuring Current AppDomain to enable Shadow Copying Assemblies
    C#
    var setup = AppDomain.CurrentDomain.SetupInformation;
    setup.ApplicationBase = _basePath;
    setup.ShadowCopyFiles = "true";
    var domain = AppDomain.CreateDomain("MyDomainName" + Guid.NewGuid(), null, setup);

    (Proposed by SpArtA)

    This seems a "natural" way provided by .NET.
    As said in MSDN article:

    This setting causes all assemblies in the application path to be copied to a download cache before they are loaded.
    The copy is locked, but the original assembly file is unlocked and can be updated.
    The Common Language Runtime automatically deletes the files when they are no longer needed.

    Optionally set a custom location for shadow copied files by using the CachePath property and the ApplicationName property.

    The base path for the location is formed by concatenating the ApplicationName property to the CachePath property as a subdirectory. Assemblies are shadow copied to subdirectories of this path, not to the base path itself.

    Pros: Requires much less code than method 2 but a bit more than method 1
    Cons: You need to consider Startup Performance issues mentioned in MSDN article

Hope this tip will help you in your projects.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)